/**
 * chatbot.service.ts — Motor de IA Multi-Agente (Chatbot Rápido).
 *
 * Padrão Cérebro (instrucoes.md §5):
 *  - Não envia histórico completo para economizar tokens
 *  - Mantém resumo de 2 frases por chat (botSummary no banco)
 *  - Micro-prompt de intenção retorna 1 token: "1" = resolver, "2" = escalar
 *
 * Providers suportados (SEC-06/SEC-07):
 *  - GEMINI  → Google Generative AI (via SDK)
 *  - OPENAI  → OpenAI Chat Completions (via fetch)
 *  O Prisma enum LLMProvider { GEMINI, OPENAI } mapeia diretamente a estes dois.
 *  A Secretária IA (ProtocolEngine) suporta adicionalmente anthropic e ollama
 *  via configuração de plugin — divergência intencional, pois os dois sistemas
 *  têm arquiteturas independentes.
 */
import { GoogleGenerativeAI } from '@google/generative-ai'
import type { WASocket } from '../whatsapp/engine'
import { prisma } from '../../infra/database/prisma'
import { logger } from '../../config/logger'
import { botRepo, chatBotState } from './chatbot.repository'
import type { Server as SocketIOServer } from 'socket.io'

// ─── Cache de clientes Gemini ─────────────────────────────────────────────────

const geminiClients = new Map<string, GoogleGenerativeAI>()

function getGeminiClient(apiKey: string): GoogleGenerativeAI {
  if (!geminiClients.has(apiKey)) {
    geminiClients.set(apiKey, new GoogleGenerativeAI(apiKey))
  }
  return geminiClients.get(apiKey)!
}

// ─── Abstração de provider (SEC-06) ──────────────────────────────────────────

type Provider = 'GEMINI' | 'OPENAI'

interface AiResult {
  text: string
  inputTokens: number
  outputTokens: number
}

async function aiCall(opts: {
  provider: Provider
  apiKey: string
  model: string
  systemPrompt: string
  userPrompt: string
  maxTokens?: number
  temperature?: number
}): Promise<AiResult> {
  const { provider, apiKey, model, systemPrompt, userPrompt, maxTokens = 300, temperature = 0.7 } = opts

  if (provider === 'OPENAI') {
    const res = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
      signal: AbortSignal.timeout(25_000),
      body: JSON.stringify({
        model: model || 'gpt-4o-mini',
        messages: [
          { role: 'system', content: systemPrompt },
          { role: 'user', content: userPrompt },
        ],
        max_tokens: maxTokens,
        temperature,
      }),
    })
    const data = (await res.json()) as any
    if (!res.ok) throw new Error(data.error?.message ?? `OpenAI ${res.status}`)
    return {
      text: (data.choices[0].message.content as string).trim(),
      inputTokens: data.usage?.prompt_tokens ?? 0,
      outputTokens: data.usage?.completion_tokens ?? 0,
    }
  }

  // Default: GEMINI
  const geminiModel = getGeminiClient(apiKey).getGenerativeModel({ model: model || 'gemini-1.5-flash' })
  const result = await geminiModel.generateContent({
    contents: [{ role: 'user', parts: [{ text: `${systemPrompt}\n\n${userPrompt}` }] }],
    generationConfig: { maxOutputTokens: maxTokens, temperature },
  })
  const usage = result.response.usageMetadata
  return {
    text: result.response.text().trim(),
    inputTokens: usage?.promptTokenCount ?? 0,
    outputTokens: usage?.candidatesTokenCount ?? 0,
  }
}

// ─── Classificação de intenção ────────────────────────────────────────────────

async function classifyIntent(opts: {
  provider: Provider; apiKey: string; model: string
  userMsg: string; summary: string | null; systemPrompt: string
}): Promise<'resolve' | 'escalate'> {
  const { provider, apiKey, model, userMsg, summary, systemPrompt } = opts
  const userPrompt = [
    `Você é um classificador de intenção para um chatbot de atendimento.`,
    `Prompt do atendente: ${systemPrompt}`,
    summary ? `Contexto da conversa: ${summary}` : '',
    `Nova mensagem do cliente: "${userMsg}"`,
    `Responda APENAS com o número: 1 = resolver, 2 = escalar humano`,
  ].filter(Boolean).join('\n')

  try {
    const { text } = await aiCall({ provider, apiKey, model, systemPrompt: '', userPrompt, maxTokens: 2, temperature: 0 })
    return text.startsWith('2') ? 'escalate' : 'resolve'
  } catch (err) {
    logger.error({ err }, '[Chatbot] Erro na classificação de intenção — assumindo resolve')
    return 'resolve'
  }
}

// ─── Geração de resposta ──────────────────────────────────────────────────────

async function generateResponse(opts: {
  provider: Provider; apiKey: string; model: string
  userMsg: string; summary: string | null; systemPrompt: string
}): Promise<AiResult> {
  const { provider, apiKey, model, userMsg, summary, systemPrompt } = opts
  const userPrompt = [
    summary ? `Contexto da conversa até agora:\n${summary}` : '',
    `Mensagem do cliente: "${userMsg}"`,
    `Responda de forma natural, breve e direta. Não use markdown.`,
  ].filter(Boolean).join('\n')

  return aiCall({ provider, apiKey, model, systemPrompt, userPrompt, maxTokens: 300, temperature: 0.7 })
}

// ─── Atualização do Cérebro (resumo de 2 frases) ─────────────────────────────

async function updateSummary(opts: {
  provider: Provider; apiKey: string; model: string
  currentSummary: string | null; userMsg: string; botMsg: string
}): Promise<string> {
  const { provider, apiKey, model, currentSummary, userMsg, botMsg } = opts
  const userPrompt = [
    currentSummary ? `Resumo atual: ${currentSummary}` : 'Primeira troca da conversa.',
    `Nova troca — Cliente: "${userMsg}" / Assistente: "${botMsg}"`,
    `Atualize o resumo em MÁXIMO 2 frases. Responda só com o resumo, sem introdução.`,
  ].join('\n')

  try {
    const { text } = await aiCall({ provider, apiKey, model, systemPrompt: '', userPrompt, maxTokens: 80, temperature: 0.3 })
    return text
  } catch {
    return currentSummary ?? ''
  }
}

// ─── Interface pública ────────────────────────────────────────────────────────

export class ChatbotService {
  constructor(private io: SocketIOServer) {}

  /**
   * Ponto de entrada para cada mensagem recebida.
   * Chamado pelo MessageHandler após persistir a mensagem.
   */
  async handleIncoming(opts: {
    tenantId: string
    instanceId: string
    chatId: string
    jid: string
    text: string
    sock: WASocket
  }): Promise<void> {
    const { tenantId, instanceId, chatId, jid, text, sock } = opts

    // 1. Verifica se há bot ativo para esta instância
    const bot = await botRepo.findEnabled(tenantId, instanceId)
    if (!bot) return

    // 2. Verifica o estado do chat (human takeover)
    const chatState = await chatBotState.getState(chatId)
    if (!chatState || chatState.botPaused) return

    // 3. Aplica modo de gatilho
    if (bot.triggerMode === 'KEYWORD') {
      const lowerText = text.toLowerCase()
      const hasKeyword = bot.keywords.some((kw) => lowerText.includes(kw.toLowerCase()))
      if (!hasKeyword) return
    }

    const provider = (bot.credential.provider ?? 'GEMINI') as Provider
    const { apiKey } = bot.credential
    const { model } = bot
    const summary = chatState.botSummary ?? null

    try {
      // 4. Micro-prompt: intenção
      const intent = await classifyIntent({ provider, apiKey, model, userMsg: text, summary, systemPrompt: bot.systemPrompt })

      if (intent === 'escalate') {
        await chatBotState.pauseBot(chatId)
        this.io.to(`chat:${chatId}`).emit('bot:escalated', {
          chatId,
          message: 'Bot pausado — atendente humano necessário',
        })
        logger.info({ chatId, jid }, '[Chatbot] Escalação para humano')
        return
      }

      // 5. Indicador de digitação (SEC-14)
      await sock.sendPresenceUpdate('composing', jid).catch(() => {})

      // 6. Gera resposta (com fallback para outro provider se falhar)
      let result: AiResult
      try {
        result = await generateResponse({ provider, apiKey, model, userMsg: text, summary, systemPrompt: bot.systemPrompt })
      } catch (primaryErr) {
        // Fallback: tenta outra credencial da mesma instância com provider diferente
        const fallbackCred = await prisma.aICredential.findFirst({
          where: { tenantId, instanceId, NOT: { id: bot.credentialId } },
        })
        if (!fallbackCred) throw primaryErr
        logger.warn({ chatId, primaryErr }, '[Chatbot] Provider primário falhou — tentando fallback')
        const fbProvider = (fallbackCred.provider ?? 'GEMINI') as Provider
        result = await generateResponse({
          provider: fbProvider, apiKey: fallbackCred.apiKey, model,
          userMsg: text, summary, systemPrompt: bot.systemPrompt,
        })
      }

      // 7. Para indicador de digitação + envia
      await sock.sendPresenceUpdate('paused', jid).catch(() => {})
      const sent = await sock.sendMessage(jid, { text: result.text })

      // 8. Persiste mensagem do bot no banco
      const chat = await prisma.chat.findUnique({ where: { id: chatId } })
      if (chat) {
        const botMsg = await prisma.message.create({
          data: {
            tenantId,
            instanceId,
            chatId,
            remoteJid: jid,
            messageId: sent?.key.id ?? `bot-${Date.now()}`,
            fromMe: true,
            type: 'TEXT',
            body: result.text,
            status: 'SENT',
            timestamp: new Date(),
          },
        })
        this.io.to(`chat:${chatId}`).emit('message:new', botMsg)
      }

      // 9. Atualiza Cérebro + telemetria (SEC-13)
      const newSummary = await updateSummary({ provider, apiKey, model, currentSummary: summary, userMsg: text, botMsg: result.text })
      await chatBotState.updateSummary(chatId, newSummary)

      logger.info({ chatId, jid, intent, provider, inputTokens: result.inputTokens, outputTokens: result.outputTokens }, '[Chatbot] Resposta enviada')
    } catch (err) {
      await sock.sendPresenceUpdate('paused', jid).catch(() => {})
      logger.error({ err, chatId }, '[Chatbot] Erro ao processar mensagem')
    }
  }
}
