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

/**
 * Bedrock Service
 *
 * AWS Bedrock integration service for AI model invocations.
 * Handles prompt formatting, API calls, and response parsing
 * for various AI models with temperature support for behavior modes.
 *
 * Supports multiple response formats:
 * - OpenAI-compatible format (choices[].message.content)
 * - Anthropic Messages API format (content[].text)
 *
 * @module backend-services/bedrock.service
 */

import {
    BedrockRuntimeClient,
    InvokeModelCommand,
    type InvokeModelCommandInput,
} from '@aws-sdk/client-bedrock-runtime';
import type { ConversationMessage } from '../types/chat.types';

/**
 * Parameters for invoking the Bedrock model.
 */
export interface InvokeParams {
    /** The current user message to process */
    message: string;
    /** Previous conversation history for context */
    history: ConversationMessage[];
    /** Relevant documents from vector search for RAG (optional) */
    relevant?: Array<{ id: string; content: string; score: number }>;
    /** Temperature for response generation (0.0-1.0) */
    temperature?: number;
}

/**
 * Chat message format for Bedrock models.
 */
interface ChatMessage {
    role: 'user' | 'assistant';
    content: string;
}

/**
 * Service for interacting with AWS Bedrock AI models.
 *
 * Provides methods to invoke AI models with conversation history,
 * temperature control for behavior modes, and optional RAG context.
 *
 * @example
 * ```typescript
 * const bedrock = new BedrockService();
 * const response = await bedrock.invoke({
 *   message: 'What is the capital of France?',
 *   history: [],
 *   temperature: 0.5, // balanced mode
 * });
 * ```
 */
export class BedrockService {
    private client: BedrockRuntimeClient;
    private modelId: string;

    /**
     * Creates a new BedrockService instance.
     *
     * Initializes the Bedrock client and configures the model ID
     * from environment variables or uses the default model.
     */
    constructor() {
        this.client = new BedrockRuntimeClient({
            region: process.env.AWS_REGION || 'us-east-1',
        });
        this.modelId =
            process.env.BEDROCK_MODEL_ID || 'openai.gpt-oss-120b-1:0';
    }

    /**
     * Invokes the Bedrock model with the given parameters.
     *
     * Formats the prompt with conversation history and relevant context,
     * calls the Bedrock InvokeModel API, and parses the response.
     *
     * @param params - The invocation parameters
     * @returns The AI-generated response text
     * @throws Error if the Bedrock API call fails or response parsing fails
     */
    async invoke(params: InvokeParams): Promise<string> {
        const { systemPrompt, messages } = this.formatPrompt(params);
        const temperature = params.temperature ?? 0.5;

        const requestBody = {
            anthropic_version: 'bedrock-2023-05-31',
            max_tokens: 4096,
            temperature,
            system: systemPrompt,
            messages,
        };

        const input: InvokeModelCommandInput = {
            modelId: this.modelId,
            contentType: 'application/json',
            accept: 'application/json',
            body: JSON.stringify(requestBody),
        };

        console.log(JSON.stringify({
            service: 'BedrockService',
            status: 'sending_request',
            modelId: this.modelId,
            messagesCount: messages.length,
            temperature,
        }));

        try {
            const command = new InvokeModelCommand(input);
            const response = await this.client.send(command);

            console.log(JSON.stringify({
                service: 'BedrockService',
                status: 'response_received',
                hasBody: !!response.body,
            }));

            return this.parseResponse(response.body);
        } catch (error) {
            console.error(JSON.stringify({
                service: 'BedrockService',
                status: 'error',
                errorName: error instanceof Error ? error.name : 'Unknown',
                errorMessage: error instanceof Error ? error.message : 'Unknown',
                errorStack: error instanceof Error ? error.stack : undefined,
            }));
            const errorMessage = this.formatError(error);
            throw new Error(`Bedrock invocation failed: ${errorMessage}`);
        }
    }

    /**
     * Formats the prompt for the AI model.
     *
     * Builds a system prompt with relevant context and converts
     * conversation history to the chat message format.
     *
     * @param params - The invocation parameters
     * @returns The formatted system prompt and messages array
     */
    private formatPrompt(params: InvokeParams): {
        systemPrompt: string;
        messages: ChatMessage[];
    } {
        const { message, history, relevant = [] } = params;

        // Build system prompt with relevant context
        let systemPrompt =
            'You are a helpful AI assistant. Answer questions accurately and concisely.';

        if (relevant.length > 0) {
            const contextSection = relevant
                .map((doc, index) => `[${index + 1}] ${doc.content}`)
                .join('\n\n');

            systemPrompt += `\n\nUse the following relevant context to help answer the user's question:\n\n${contextSection}\n\nIf the context is relevant, incorporate it into your response. If not, answer based on your general knowledge.`;
        }

        // Convert history to chat message format
        const messages: ChatMessage[] = history.map((msg) => ({
            role: msg.role,
            content: msg.content,
        }));

        // Add current user message
        messages.push({
            role: 'user',
            content: message,
        });

        return { systemPrompt, messages };
    }

    /**
     * Parses the response from the Bedrock API.
     *
     * Supports multiple response formats:
     * - OpenAI-compatible: choices[].message.content
     * - Anthropic Messages API: content[].text
     *
     * @param body - The raw response body from Bedrock
     * @returns The extracted text response
     * @throws Error if the response format is invalid
     */
    private parseResponse(body: Uint8Array | undefined): string {
        if (!body) {
            throw new Error('Empty response body from Bedrock');
        }

        const responseText = new TextDecoder().decode(body);
        console.log(JSON.stringify({
            service: 'BedrockService',
            status: 'parsing_response',
            responsePreview: responseText.substring(0, 500),
        }));

        const response = JSON.parse(responseText);

        // Handle OpenAI-compatible format (choices[].message.content)
        if (response.choices && Array.isArray(response.choices) && response.choices.length > 0) {
            const choice = response.choices[0];
            if (choice.message?.content) {
                console.log(JSON.stringify({
                    service: 'BedrockService',
                    status: 'parsed_openai_format',
                    contentLength: choice.message.content.length,
                }));
                return choice.message.content;
            }
        }

        // Handle Anthropic Messages API format (content[].text)
        if (response.content && Array.isArray(response.content) && response.content.length > 0) {
            const textContent = response.content.find((c: { type: string; text?: string }) => c.type === 'text');
            if (textContent?.text) {
                console.log(JSON.stringify({
                    service: 'BedrockService',
                    status: 'parsed_anthropic_format',
                    contentLength: textContent.text.length,
                }));
                return textContent.text;
            }
        }

        console.log(JSON.stringify({
            service: 'BedrockService',
            status: 'unknown_format',
            responseKeys: Object.keys(response),
        }));

        throw new Error('No content in Bedrock response - unknown response format');
    }

    /**
     * Formats an error into a meaningful error message.
     *
     * Handles various error types from the Bedrock API and
     * provides user-friendly error messages.
     *
     * @param error - The error to format
     * @returns A formatted error message
     */
    private formatError(error: unknown): string {
        if (error instanceof Error) {
            const errorName = error.name;

            switch (errorName) {
                case 'AccessDeniedException':
                    return 'Access denied to Bedrock model. Check IAM permissions.';
                case 'ResourceNotFoundException':
                    return `Model not found: ${this.modelId}. Verify the model ID is correct.`;
                case 'ThrottlingException':
                    return 'Request throttled. Please retry after a short delay.';
                case 'ModelTimeoutException':
                    return 'Model invocation timed out. Try a shorter prompt.';
                case 'ModelStreamErrorException':
                    return 'Error streaming model response.';
                case 'ValidationException':
                    return `Invalid request: ${error.message}`;
                case 'ServiceQuotaExceededException':
                    return 'Service quota exceeded. Request a quota increase.';
                default:
                    return error.message;
            }
        }

        return 'Unknown error occurred';
    }
}
