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

/**
 * Chat Handlers
 *
 * Lambda handlers for the AI chat endpoints.
 * Includes chat, health, and streaming handlers.
 *
 * @module handlers/chat.handlers
 */

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

/**
 * Ties type definition for the chat handler.
 */
type ChatTies = {
    bedrock: BedrockService;
    bedrockStream: BedrockStreamService;
    memory: MemoryService;
};

/**
 * Behavior mode to temperature mapping.
 */
const BEHAVIOR_TEMPERATURE_MAP: Record<string, number> = {
    precise: 0.1,
    balanced: 0.5,
    creative: 0.9,
};

/**
 * Creates CORS headers with configurable origin.
 */
const getCorsHeaders = (): Record<string, string> => ({
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': process.env.ALLOWED_ORIGIN || '*',
    'Access-Control-Allow-Headers': 'Content-Type,Authorization',
    'Access-Control-Allow-Methods': 'POST,GET,OPTIONS',
});

/**
 * Creates a JSON response with CORS headers.
 */
const createResponse = (
    statusCode: number,
    body: Record<string, unknown>
): APIGatewayProxyResult => ({
    statusCode,
    headers: getCorsHeaders(),
    body: JSON.stringify(body),
});

/**
 * Chat handler Lambda definition.
 *
 * Handles POST /chat requests to process user messages through the AI pipeline:
 * 1. Validates request (sessionId, message)
 * 2. Retrieves conversation history from DynamoDB
 * 3. Invokes Bedrock AI model with message, history, and temperature
 * 4. Persists the conversation turn to session memory
 * 5. Returns the AI response
 *
 * Logging includes requestId, sessionId, and latency but NOT message content.
 */
export const chatHandler: LambdaDefinition<ChatTies> = {
    id: 'chat',
    ties: {
        bedrock: BedrockService,
        bedrockStream: BedrockStreamService,
        memory: MemoryService,
    },
    handler: async (event: LambdaEvent<ChatTies>): Promise<APIGatewayProxyResult> => {
        const requestId = event.requestContext?.requestId || 'unknown';
        const startTime = Date.now();

        // 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 sessionId
        if (!body.sessionId?.trim()) {
            return createResponse(400, { error: 'sessionId is required' });
        }

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

        const { bedrock, memory } = event.ties;
        const behavior = body.behavior || 'balanced';
        const temperature = BEHAVIOR_TEMPERATURE_MAP[behavior] ?? 0.5;

        try {
            // Log request metadata (not message content for privacy)
            console.log(
                JSON.stringify({
                    requestId,
                    sessionId: body.sessionId,
                    behavior,
                    messageLength: body.message.length,
                })
            );

            // Get conversation history
            const history = await memory.get(body.sessionId);

            // Invoke Bedrock with temperature based on behavior mode
            const response = await bedrock.invoke({
                message: body.message,
                history,
                relevant: [],
                temperature,
            });

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

            const latency = Date.now() - startTime;

            // Log response metadata (not content)
            console.log(
                JSON.stringify({
                    requestId,
                    sessionId: body.sessionId,
                    latency,
                    responseLength: response.length,
                })
            );

            const chatResponse: ChatResponse = {
                sessionId: body.sessionId,
                response,
                timestamp: new Date().toISOString(),
            };

            return createResponse(200, chatResponse as unknown as Record<string, unknown>);
        } catch (error) {
            const latency = Date.now() - startTime;

            // Log error metadata (not sensitive details)
            console.error(
                JSON.stringify({
                    requestId,
                    sessionId: body.sessionId,
                    latency,
                    error: error instanceof Error ? error.message : 'Unknown',
                })
            );

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

/**
 * Health check handler Lambda definition.
 *
 * Returns 200 with status and timestamp for health monitoring.
 */
export const healthHandler: LambdaDefinition<Record<string, never>> = {
    id: 'health',
    ties: {},
    handler: async (): Promise<APIGatewayProxyResult> => {
        return createResponse(200, {
            status: 'healthy',
            timestamp: new Date().toISOString(),
        });
    },
    http: {
        method: 'GET',
        path: '/health',
        auth: 'none',
        cors: true,
    },
};

/**
 * Streaming chat handler Lambda definition (optional).
 *
 * Handles POST /chat/stream requests with Server-Sent Events (SSE)
 * for real-time token streaming.
 *
 * Note: Requires API Gateway HTTP API with response streaming support.
 */
export const streamHandler: LambdaDefinition<ChatTies> = {
    id: 'chat-stream',
    ties: {
        bedrock: BedrockService,
        bedrockStream: BedrockStreamService,
        memory: MemoryService,
    },
    handler: async (event: LambdaEvent<ChatTies>): Promise<APIGatewayProxyResult> => {
        const requestId = event.requestContext?.requestId || 'unknown';

        // 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 sessionId
        if (!body.sessionId?.trim()) {
            return createResponse(400, { error: 'sessionId is required' });
        }

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

        const { bedrockStream, memory } = event.ties;
        const behavior = body.behavior || 'balanced';
        const temperature = BEHAVIOR_TEMPERATURE_MAP[behavior] ?? 0.5;

        try {
            // Log request metadata
            console.log(
                JSON.stringify({
                    requestId,
                    sessionId: body.sessionId,
                    behavior,
                    streaming: true,
                    messagePreview: body.message.substring(0, 50),
                })
            );

            // Get conversation history
            const history = await memory.get(body.sessionId);
            console.log(JSON.stringify({ requestId, historyLength: history.length }));

            // Collect streamed response
            let fullResponse = '';
            const chunks: string[] = [];
            let chunkCount = 0;

            console.log(JSON.stringify({ requestId, status: 'starting_stream' }));

            for await (const token of bedrockStream.invokeStream({
                message: body.message,
                history,
                temperature,
            })) {
                chunkCount++;
                fullResponse += token;
                chunks.push(`data: ${JSON.stringify({ token })}\n\n`);

                // Log first few chunks for debugging
                if (chunkCount <= 3) {
                    console.log(JSON.stringify({ requestId, chunkCount, tokenLength: token.length }));
                }
            }

            console.log(JSON.stringify({
                requestId,
                status: 'stream_complete',
                totalChunks: chunkCount,
                responseLength: fullResponse.length
            }));

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

            // Add done marker
            chunks.push('data: [DONE]\n\n');

            const responseBody = chunks.join('');
            console.log(JSON.stringify({
                requestId,
                status: 'returning_response',
                bodyLength: responseBody.length,
                chunksCount: chunks.length
            }));

            // Return SSE response
            // Note: For true streaming, use Lambda response streaming or API Gateway WebSocket
            return {
                statusCode: 200,
                headers: {
                    ...getCorsHeaders(),
                    'Content-Type': 'text/event-stream',
                    'Cache-Control': 'no-cache',
                    Connection: 'keep-alive',
                },
                body: responseBody,
            };
        } catch (error) {
            console.error(
                JSON.stringify({
                    requestId,
                    sessionId: body.sessionId,
                    error: error instanceof Error ? error.message : 'Unknown',
                    errorStack: error instanceof Error ? error.stack : undefined,
                })
            );

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