import { PassThrough } from "stream";

import { AIMessage, HumanMessage } from "@langchain/core/messages";
import { Messages } from "@langchain/langgraph";
import { Message } from "ai";
import { ElevenLabsClient } from "elevenlabs";

import { graph } from "@/lib/graph";
import logger from "@/lib/logger";

const elevenlabs = new ElevenLabsClient({
  apiKey: process.env.ELEVENLABS_API_KEY,
});

// Configure voice settings
const VOICE_ID = process.env.ELEVENLABS_VOICE_ID || "21m00Tcm4TlvDq8ikWAM"; // Default to Rachel voice
const VOICE_SETTINGS = {
  stability: 0.5,
  similarity_boost: 0.75,
  style: 0.0,
  use_speaker_boost: true,
};

// Function to estimate audio duration from text (rough approximation)
function estimateAudioDuration(text: string): number {
  // Average speaking rate is about 150-160 words per minute
  const wordsPerMinute = 150;
  const words = text.split(/\s+/).length;
  const minutes = words / wordsPerMinute;
  return minutes * 60 * 1000; // Convert to milliseconds
}

// Simple viseme generation (basic approximation)
function generateApproximateVisemes(text: string, audioDurationMs: number): [number, number][] {
  // Basic phoneme to viseme mapping
  const phonemeToViseme: { [key: string]: number } = {
    'sil': 0, 'p': 1, 'b': 1, 'm': 1, 'f': 2, 'v': 2,
    'th': 3, 't': 4, 'd': 4, 'n': 4, 'l': 4, 's': 5,
    'z': 5, 'sh': 6, 'k': 7, 'g': 7, 'r': 8, 'aa': 9,
    'ih': 10, 'uw': 11, 'ah': 12, 'ae': 13, 'eh': 14,
    'ow': 15, 'ay': 16, 'er': 17, 'w': 18
  };

  const words = text.toLowerCase().replace(/[^\w\s]/g, '').split(/\s+/);
  const phonemes: string[] = [];
  
  for (const word of words) {
    for (let i = 0; i < word.length; i++) {
      const char = word[i];
      if ('aeiou'.includes(char)) {
        phonemes.push('ah');
      } else if ('pbm'.includes(char)) {
        phonemes.push('p');
      } else if ('fv'.includes(char)) {
        phonemes.push('f');
      } else if ('tdnl'.includes(char)) {
        phonemes.push('t');
      } else if ('sz'.includes(char)) {
        phonemes.push('s');
      } else if ('kg'.includes(char)) {
        phonemes.push('k');
      } else if (char === 'r') {
        phonemes.push('r');
      } else {
        phonemes.push('t');
      }
    }
    phonemes.push('sil');
  }
  
  const visemes: [number, number][] = [];
  const timePerPhoneme = audioDurationMs / phonemes.length;
  
  phonemes.forEach((phoneme, index) => {
    const visemeId = phonemeToViseme[phoneme] || 0;
    const timestamp = index * timePerPhoneme;
    visemes.push([timestamp, visemeId]);
  });
  
  return visemes;
}

// Define POST method for chat route
export async function POST(req: Request) {
  try {
    const {
      messages,
    }: {
      messages: Message[];
    } = await req.json();

    // TODO: Filter to only include last message when using langgraph memory
    const allMessages: Messages = messages.map((message) =>
      message.role === "user" ? new HumanMessage(message.content) : new AIMessage(message.content)
    );

    // Stream of messages
    const result = await graph.invoke({ messages: allMessages });
    const lastMessage = result.messages[result.messages.length - 1];
    const messageText = lastMessage.content.toString();

    // Use ElevenLabs to synthesize speech
    const audioStream = await elevenlabs.generate({
      voice: VOICE_ID,
      text: messageText,
      model_id: "eleven_multilingual_v2",
      voice_settings: VOICE_SETTINGS,
    });

    // Convert the audio stream to a PassThrough stream
    const bufferStream = new PassThrough();
    
    // ElevenLabs returns an async iterator, so we need to collect the chunks
    const chunks: Uint8Array[] = [];
    for await (const chunk of audioStream) {
      chunks.push(chunk);
    }
    
    // Combine all chunks into a single buffer
    const audioBuffer = Buffer.concat(chunks);
    bufferStream.end(audioBuffer);

    // Generate approximate visemes
    const estimatedDuration = estimateAudioDuration(messageText);
    const visemes = generateApproximateVisemes(messageText, estimatedDuration);

    logger.info(`Response: ${lastMessage.content}`);
    
    return new Response(bufferStream, {
      headers: {
        "Content-Type": "audio/mpeg",
        "Content-Disposition": `inline; filename=tts.mp3`,
        Visemes: JSON.stringify(visemes),
        Message: JSON.stringify({
          id: lastMessage.id,
          role: "assistant",
          content: lastMessage.content,
        }),
      },
    });
  } catch (error) {
    logger.error("Error in text route:", error);
    return new Response(JSON.stringify({ error: "Internal server error" }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
}