import Anthropic from '@anthropic-ai/sdk';
import { Injectable, Logger } 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 { SISTEMA_EXTRACTOR } from './prompts';
import { leer, POR_DEFECTO } from './extraccion';
import { CATEGORIAS, Extraccion, INTENCIONES } from './tipos';

const ESQUEMA = {
  type: 'object',
  properties: {
    intencion: { type: 'string', enum: [...INTENCIONES] },
    tipo_documento: { type: 'string', enum: ['FACTURA', 'NOTA_CREDITO', 'NOTA_REMISION', 'NINGUNO'] },
    referencia: { type: 'string' },
    categoria: { type: 'string', enum: [...CATEGORIAS] },
  },
  required: ['intencion', 'tipo_documento', 'referencia', 'categoria'],
  additionalProperties: false,
} as const;

export interface ResultadoExtractor {
  extraccion: Extraccion;
  uso: UsoTokens | null;
  modelo: string;
  ms: number;
}

/**
 * Primera llamada del turno: texto libre → JSON con esquema fijo (structured
 * outputs). Barata y determinística: effort bajo, sin herramientas. Si falla o
 * el modelo rechaza, sigue con una extracción neutra (la máquina de estados
 * pregunta).
 */
@Injectable()
export class ExtractorService {
  private readonly logger = new Logger(ExtractorService.name);

  constructor(
    private readonly llm: AnthropicService,
    @Optional() private readonly config?: ConfigOperativaService,
  ) {}

  /** `modeloForzado`: solo para comparar modelos (pnpm eval:extractor). */
  async extraer(contexto: string[], texto: string, modeloForzado?: string): Promise<ResultadoExtractor> {
    const inicio = Date.now();
    const modelo = modeloForzado ?? (await this.config?.modelo('extractor')) ?? envs.extractor.model;
    if (!this.llm.client) return { extraccion: POR_DEFECTO, uso: null, modelo, ms: 0 };

    const previo = contexto.length
      ? `Contexto (mensajes anteriores del hilo, solo como referencia):\n${contexto.join('\n')}\n\n`
      : '';
    try {
      const respuesta = await this.llm.client.beta.messages.create({
        model: modelo,
        max_tokens: envs.extractor.maxTokens,
        ...FALLBACK,
        output_config: {
          effort: envs.extractor.effort,
          format: { type: 'json_schema', schema: ESQUEMA as unknown as Record<string, unknown> },
        },
        system: SISTEMA_EXTRACTOR,
        messages: [{ role: 'user', content: `${previo}<mensaje_cliente>\n${texto}\n</mensaje_cliente>` }],
      });
      const uso = respuesta.usage as UsoTokens;
      if (respuesta.stop_reason === 'refusal') {
        return { extraccion: POR_DEFECTO, uso, modelo: respuesta.model, ms: Date.now() - inicio };
      }
      const bloque = respuesta.content.find((b): b is Anthropic.Beta.BetaTextBlock => b.type === 'text');
      return {
        extraccion: leer(bloque ? JSON.parse(bloque.text) : null),
        uso,
        modelo: respuesta.model,
        ms: Date.now() - inicio,
      };
    } catch (err) {
      this.logger.warn(`Extractor falló, se sigue sin extracción: ${err instanceof Error ? err.message : String(err)}`);
      return { extraccion: POR_DEFECTO, uso: null, modelo, ms: Date.now() - inicio };
    }
  }
}
