import { Injectable, Logger } from '@nestjs/common';
import {
  type CanalWhatsApp, type EnvioResult, type IWhatsAppProvider, type ProveedorWhatsApp,
  type WebhookEvento,
} from './whatsapp-provider.interface';

/**
 * Conector Evolution API, en sus dos modos (spec §6.6.1):
 *
 * - `evolution_cloud`: Evolution como gateway hacia la API **oficial** de Meta.
 * - `evolution_baileys`: vincula el número por QR con un protocolo **no oficial**.
 *   Sirve para demos y para desarrollar F2 sin esperar la verificación de Meta,
 *   pero los envíos automáticos están bloqueados aguas arriba (regla D4): acá no
 *   se valida eso, lo hace `MensajeriaService`, que es quien sabe si el envío es
 *   automático o lo escribió una persona.
 *
 * Los dos modos hablan la misma API REST de Evolution, así que es una sola clase
 * con el `tipo` inyectado. Separarlas sería duplicar el cliente HTTP para
 * cambiar una constante.
 */
@Injectable()
export class EvolutionProvider implements IWhatsAppProvider {
  private readonly logger = new Logger(EvolutionProvider.name);

  constructor(readonly tipo: ProveedorWhatsApp = 'evolution_cloud') {}

  enviarTexto(canal: CanalWhatsApp, para: string, texto: string): Promise<EnvioResult> {
    return this.post(canal, 'message/sendText', { number: para, text: texto });
  }

  /**
   * Evolution en modo Baileys **no tiene plantillas**: no existen en el
   * protocolo no oficial. Se degrada al cuerpo con las variables sustituidas,
   * que es lo que el destinatario vería igual, y se avisa en el log para que no
   * parezca que se envió una plantilla aprobada.
   */
  enviarPlantilla(
    canal: CanalWhatsApp,
    para: string,
    plantilla: string,
    idioma: string,
    variables: string[],
  ): Promise<EnvioResult> {
    if (this.tipo === 'evolution_baileys') {
      this.logger.warn(
        `Canal ${canal.id} en Baileys: la plantilla "${plantilla}" se envía como texto plano (el modo no soporta plantillas)`,
      );
      return this.enviarTexto(canal, para, variables.join(' '));
    }
    return this.post(canal, 'message/sendTemplate', {
      number: para,
      name: plantilla,
      language: idioma,
      components: variables.map((v) => ({ type: 'text', text: v })),
    });
  }

  enviarAdjunto(
    canal: CanalWhatsApp,
    para: string,
    url: string,
    mime: string,
    descripcion?: string,
  ): Promise<EnvioResult> {
    return this.post(canal, 'message/sendMedia', {
      number: para,
      mediatype: mime.startsWith('image/') ? 'image' : mime.startsWith('audio/') ? 'audio' : 'document',
      media: url,
      ...(descripcion ? { caption: descripcion } : {}),
    });
  }

  /**
   * Evolution no firma sus webhooks: el origen se asegura con la URL, que lleva
   * el id del canal y es por donde entra (`/crm/webhooks/whatsapp/evolution/:canalId`).
   * No es equivalente a la firma de Meta y conviene saberlo: por eso este modo
   * es sólo para pruebas y la URL no debería publicarse.
   */
  parsearWebhook(
    _headers: Record<string, string | undefined>,
    cuerpoCrudo: string,
    canal: CanalWhatsApp,
  ): WebhookEvento[] {
    let cuerpo: EvolutionWebhook;
    try {
      cuerpo = JSON.parse(cuerpoCrudo) as EvolutionWebhook;
    } catch {
      this.logger.warn(`Webhook de Evolution con cuerpo no parseable (canal ${canal.id})`);
      return [];
    }

    const d = cuerpo.data;
    if (!d) return [];

    // Evolution manda un evento por POST, no un lote como Meta.
    if (cuerpo.event === 'messages.upsert' && d.key && !d.key.fromMe) {
      const texto = d.message?.conversation ?? d.message?.extendedTextMessage?.text ?? null;
      const media = d.message?.imageMessage ?? d.message?.documentMessage ?? d.message?.audioMessage ?? null;
      return [
        {
          clase: 'mensaje',
          identificador_externo: cuerpo.instance ?? null,
          // El JID viene como "595981123456@s.whatsapp.net".
          telefono: (d.key.remoteJid ?? '').split('@')[0],
          nombre_contacto: d.pushName ?? null,
          id_externo: d.key.id ?? '',
          tipo: texto ? 'texto' : media ? 'adjunto' : 'otro',
          texto: texto ?? media?.caption ?? null,
          adjunto_url: media?.url ?? null,
          adjunto_mime: media?.mimetype ?? null,
          fecha: d.messageTimestamp ? new Date(Number(d.messageTimestamp) * 1000) : new Date(),
        },
      ];
    }

    if (cuerpo.event === 'messages.update' && d.key) {
      return [
        {
          clase: 'estado',
          identificador_externo: cuerpo.instance ?? null,
          id_externo: d.key.id ?? '',
          estado: MAPA_ESTADO[d.status ?? ''] ?? 'enviado',
          fecha: new Date(),
        },
      ];
    }

    return [];
  }

  /** En Baileys la sesión se cae y hay que volver a escanear el QR. */
  async estadoSesion(canal: CanalWhatsApp): Promise<'conectado' | 'desconectado' | 'qr_pendiente'> {
    const { base_url, instancia, api_key } = canal.credenciales;
    if (!base_url || !instancia) return 'desconectado';
    try {
      const r = await fetch(`${base_url.replace(/\/$/, '')}/instance/connectionState/${instancia}`, {
        headers: { apikey: api_key ?? '' },
      });
      if (!r.ok) return 'desconectado';
      const data = (await r.json()) as { instance?: { state?: string } };
      const estado = data.instance?.state;
      if (estado === 'open') return 'conectado';
      if (estado === 'connecting') return 'qr_pendiente';
      return 'desconectado';
    } catch {
      return 'desconectado';
    }
  }

  // ---------------------------------------------------------------------------

  private async post(canal: CanalWhatsApp, ruta: string, cuerpo: Record<string, unknown>): Promise<EnvioResult> {
    const { base_url, instancia, api_key } = canal.credenciales;
    if (!base_url || !instancia) {
      return { id_externo: null, estado: 'rechazado', error: 'El canal no tiene base_url o instancia' };
    }

    try {
      const r = await fetch(`${base_url.replace(/\/$/, '')}/${ruta}/${instancia}`, {
        method: 'POST',
        headers: { apikey: api_key ?? '', 'Content-Type': 'application/json' },
        body: JSON.stringify(cuerpo),
      });
      const data = (await r.json()) as { key?: { id?: string }; message?: string };

      if (!r.ok) {
        const error = data?.message ?? `HTTP ${r.status}`;
        this.logger.warn(`Evolution rechazó el envío del canal ${canal.id}: ${error}`);
        return { id_externo: null, estado: 'rechazado', error };
      }
      return { id_externo: data.key?.id ?? null, estado: 'encolado' };
    } catch (e) {
      const error = e instanceof Error ? e.message : String(e);
      this.logger.error(`No se pudo contactar a Evolution para el canal ${canal.id}: ${error}`);
      return { id_externo: null, estado: 'rechazado', error };
    }
  }
}

const MAPA_ESTADO: Record<string, 'enviado' | 'entregado' | 'leido' | 'fallido'> = {
  SERVER_ACK: 'enviado',
  DELIVERY_ACK: 'entregado',
  READ: 'leido',
  ERROR: 'fallido',
};

// ── Forma del payload de Evolution, sólo lo que se usa ───────────────────────

interface EvolutionMedia {
  url?: string;
  mimetype?: string;
  caption?: string;
}

interface EvolutionWebhook {
  event?: string;
  instance?: string;
  data?: {
    key?: { id?: string; remoteJid?: string; fromMe?: boolean };
    pushName?: string;
    messageTimestamp?: number | string;
    status?: string;
    message?: {
      conversation?: string;
      extendedTextMessage?: { text?: string };
      imageMessage?: EvolutionMedia;
      documentMessage?: EvolutionMedia;
      audioMessage?: EvolutionMedia;
    };
  };
}
