import Anthropic from '@anthropic-ai/sdk';
import { Injectable } from '@nestjs/common';
import { envs } from 'src/config/envs';
import { Optional } from '@nestjs/common';
import { ConfigOperativaService } from 'src/config-operativa/config-operativa.service';
import { AnthropicService, FALLBACK } from 'src/llm/anthropic.service';
import { UsoTokens } from 'src/llm/precios';
import { DEFINICIONES, HerramientasTurno, PROGRESO } from './herramientas';
import { FiltroCierre } from './cierre';
import { paraEco } from './eco';
import { sistemaRedactor } from './prompts';
import { Cierre } from './tipos';

type Mensaje = Anthropic.Beta.BetaMessageParam;

/** Máximo de idas y vueltas con herramientas por turno: corta loops raros. */
const MAX_ITERACIONES = 6;

export type EventoRedactor = { tipo: 'progreso'; texto: string } | { tipo: 'texto'; delta: string };

export interface ResultadoRedactor {
  texto: string;
  uso: UsoTokens;
  modelo: string;
  primerTokenMs: number | null;
  rechazo: boolean;
  cierre: Cierre | null;
}

/**
 * Segunda llamada del turno: redacta la respuesta en streaming, con el
 * catálogo como herramientas. Loop manual (no el tool runner) para emitir
 * progreso por herramienta y controlar el echo después de un fallback.
 */
@Injectable()
export class RedactorService {
  constructor(
    private readonly llm: AnthropicService,
    @Optional() private readonly config?: ConfigOperativaService,
  ) {}

  async *redactar(entrada: {
    mensajes: Mensaje[];
    herramientas: HerramientasTurno;
    telefono: string;
  }): AsyncGenerator<EventoRedactor, ResultadoRedactor> {
    const client = this.llm.client;
    if (!client) throw new Error('LLM no disponible');

    const inicio = Date.now();
    const mensajes = [...entrada.mensajes];
    const uso: UsoTokens = {
      input_tokens: 0,
      output_tokens: 0,
      cache_read_input_tokens: 0,
      cache_creation_input_tokens: 0,
    };
    const modeloElegido = (await this.config?.modelo('redactor')) ?? envs.redactor.model;
    let modelo = modeloElegido;
    let primerTokenMs: number | null = null;
    let rechazo = false;
    const filtro = new FiltroCierre();

    for (let i = 0; i < MAX_ITERACIONES; i++) {
      const stream = client.beta.messages.stream({
        model: modeloElegido,
        max_tokens: envs.redactor.maxTokens,
        ...FALLBACK,
        output_config: { effort: envs.redactor.effort },
        // Estable → cacheable: herramientas y system no cambian entre pedidos.
        system: [{ type: 'text', text: sistemaRedactor(entrada.telefono), cache_control: { type: 'ephemeral' } }],
        tools: DEFINICIONES,
        messages: mensajes,
      });

      for await (const ev of stream) {
        if (ev.type === 'content_block_start' && ev.content_block.type === 'tool_use') {
          const etiqueta = PROGRESO[ev.content_block.name];
          if (etiqueta) yield { tipo: 'progreso', texto: etiqueta };
        } else if (ev.type === 'content_block_delta' && ev.delta.type === 'text_delta') {
          const visible = filtro.agregar(ev.delta.text);
          if (visible) {
            if (primerTokenMs === null) primerTokenMs = Date.now() - inicio;
            yield { tipo: 'texto', delta: visible };
          }
        }
      }

      const mensaje = await stream.finalMessage();
      modelo = mensaje.model;
      sumarUso(uso, mensaje.usage);

      if (mensaje.stop_reason === 'refusal') {
        rechazo = true;
        break;
      }
      const usos = mensaje.content.filter((b): b is Anthropic.Beta.BetaToolUseBlock => b.type === 'tool_use');
      // Cualquier otro stop (end_turn, max_tokens…) termina el turno: un input cortado nunca se ejecuta.
      if (mensaje.stop_reason !== 'tool_use' || usos.length === 0) break;

      mensajes.push({ role: 'assistant', content: paraEco(mensaje.content) as Anthropic.Beta.BetaContentBlockParam[] });
      const resultados: Anthropic.Beta.BetaToolResultBlockParam[] = [];
      for (const u of usos) {
        const r = await entrada.herramientas.ejecutar(u.name, u.input);
        resultados.push({
          type: 'tool_result',
          tool_use_id: u.id,
          content: r.contenido,
          is_error: r.error || undefined,
        });
      }
      // Todos los resultados en un solo mensaje (si no, el modelo deja de paralelizar).
      mensajes.push({ role: 'user', content: resultados });
    }

    const { pendiente, visible, cierre } = filtro.finalizar();
    if (pendiente) yield { tipo: 'texto', delta: pendiente };
    return { texto: visible, uso, modelo, primerTokenMs, rechazo, cierre };
  }
}

function sumarUso(total: UsoTokens, parcial: UsoTokens) {
  total.input_tokens += parcial.input_tokens ?? 0;
  total.output_tokens += parcial.output_tokens ?? 0;
  total.cache_read_input_tokens = (total.cache_read_input_tokens ?? 0) + (parcial.cache_read_input_tokens ?? 0);
  total.cache_creation_input_tokens =
    (total.cache_creation_input_tokens ?? 0) + (parcial.cache_creation_input_tokens ?? 0);
}
