/*
 * Runtime Web – Unified Serverless Framework for Full-Stack TypeScript Applications
 * Documentation: https://runtimeweb.com/docs/getting-started
 *
 * @template: ai
 */

/**
 * Chat Handler
 *
 * Lambda handler for the AI chat endpoint.
 * Orchestrates conversation flow: history retrieval → vector search → AI invocation → memory persistence.
 *
 * @module handlers/chat.handlers
 */

import type { LambdaDefinition, LambdaEvent } from '@worktif/runtime';
import type { APIGatewayProxyResult } from 'aws-lambda';
import { BedrockService } from '../services/bedrock.service';
import { MemoryService } from '../services/memory.service';
import { SearchService } from '../services/search.service';
import type { ChatRequest, ChatResponse } from '../types/chat.types';

/**
 * Ties type definition for the chat handler.
 * Defines the dependencies injected into the handler via the DI container.
 */
type ChatTies = {
    /** AWS Bedrock service for AI model invocations */
    bedrock: BedrockService;
    /** DynamoDB-based service for conversation session memory */
    memory: MemoryService;
    /** OpenSearch-based service for vector similarity search (RAG) */
    search: SearchService;
};

/**
 * Default response headers with CORS support.
 */
const RESPONSE_HEADERS = {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': 'Content-Type,Authorization',
};

/**
 * Creates a JSON response with standard headers.
 *
 * @param statusCode - HTTP status code
 * @param body - Response body object
 * @returns APIGatewayProxyResult
 */
const createResponse = (statusCode: number, body: Record<string, unknown>): APIGatewayProxyResult => ({
    statusCode,
    headers: RESPONSE_HEADERS,
    body: JSON.stringify(body),
});

/**
 * Chat handler Lambda definition.
 *
 * Handles POST /chat requests to process user messages through the AI pipeline:
 * 1. Retrieves conversation history from DynamoDB
 * 2. Performs vector search for relevant context (RAG)
 * 3. Invokes Bedrock AI model with message, history, and context
 * 4. Persists the conversation turn to session memory
 *
 * @example Request
 * ```json
 * POST /chat
 * {
 *   "sessionId": "user-123-session-456",
 *   "message": "What is the capital of France?"
 * }
 * ```
 *
 * @example Response
 * ```json
 * {
 *   "sessionId": "user-123-session-456",
 *   "response": "The capital of France is Paris.",
 *   "timestamp": "2024-01-15T10:30:00.000Z"
 * }
 * ```
 */
export const chatHandler: LambdaDefinition<ChatTies> = {
    id: 'chat',
    ties: {
        bedrock: BedrockService,
        memory: MemoryService,
        search: SearchService,
    },
    handler: async (event: LambdaEvent<ChatTies>): Promise<APIGatewayProxyResult> => {
        // Parse request body
        let body: ChatRequest;
        try {
            body = JSON.parse(event.body || '{}') as ChatRequest;
        } catch {
            return createResponse(400, { error: 'Invalid JSON in request body' });
        }

        // Validate required fields
        if (!body.sessionId) {
            return createResponse(400, { error: 'sessionId is required' });
        }

        if (!body.message) {
            return createResponse(400, { error: 'message is required' });
        }

        // Validate sessionId format (non-empty string)
        if (typeof body.sessionId !== 'string' || body.sessionId.trim().length === 0) {
            return createResponse(400, { error: 'sessionId must be a non-empty string' });
        }

        // Validate message format (non-empty string)
        if (typeof body.message !== 'string' || body.message.trim().length === 0) {
            return createResponse(400, { error: 'message must be a non-empty string' });
        }

        const { bedrock, memory, search } = event.ties;

        try {
            // Step 1: Get conversation history from DynamoDB
            const history = await memory.get(body.sessionId);

            // Step 2: Search for relevant context via vector search (RAG)
            // Returns empty array if OpenSearch is unavailable (graceful degradation)
            const relevant = await search.knn(body.message, 5);

            // Step 3: Invoke Bedrock AI model with message, history, and context
            const response = await bedrock.invoke({
                message: body.message,
                history,
                relevant,
            });

            // Step 4: Append conversation turn to session memory
            await memory.append(body.sessionId, {
                user: body.message,
                assistant: response,
            });

            // Build and return success response
            const chatResponse: ChatResponse = {
                sessionId: body.sessionId,
                response,
                timestamp: new Date().toISOString(),
            };

            return createResponse(200, chatResponse as unknown as Record<string, unknown>);
        } catch (error) {
            // Log error for debugging (CloudWatch)
            console.error('Chat handler error:', error);

            // Return generic 500 error to client (don't expose internal details)
            const errorMessage = error instanceof Error ? error.message : 'Unknown error';
            console.error('Error details:', errorMessage);

            return createResponse(500, { error: 'Internal server error' });
        }
    },
    http: {
        method: 'POST',
        path: '/chat',
        auth: 'none',
        cors: true,
    },
};
