import {
  Injectable,
  Logger,
  NotFoundException,
  BadRequestException,
} from "@nestjs/common";
import { DbService } from "../db/db.service";
import { EvolutionClient } from "../evolution/evolution.client";

const WEBHOOK_EVENTS = [
  "QRCODE_UPDATED",
  "CONNECTION_UPDATE",
  "MESSAGES_SET",
  "MESSAGES_UPSERT",
  "MESSAGES_UPDATE",
  "MESSAGES_DELETE",
  "SEND_MESSAGE",
  "CHATS_UPSERT",
  "CHATS_SET",
];

@Injectable()
export class InstancesService {
  private readonly logger = new Logger(InstancesService.name);

  constructor(
    private readonly db: DbService,
    private readonly evolution: EvolutionClient,
  ) {}

  instanceNameForUsuario(usuarioId: string) {
    const safe = usuarioId.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 40);
    return `hubee-${safe || "user"}`;
  }

  async findByUsuario(usuarioId: string) {
    const res = await this.db.query(
      `SELECT id, hubee_usuario_id, evolution_instance_name, phone_jid, status,
              last_qr_base64, created_at, updated_at
       FROM hubee_wpp.instances WHERE hubee_usuario_id = $1 LIMIT 1`,
      [usuarioId],
    );
    return res.rows[0] || null;
  }

  private sleep(ms: number) {
    return new Promise((r) => setTimeout(r, ms));
  }

  private webhookConfig() {
    const webhookUrl =
      process.env.WEBHOOK_PUBLIC_URL ||
      "http://hubee-wpp-sync:8090/webhooks/evolution";
    return {
      enabled: true,
      url: webhookUrl,
      byEvents: false,
      base64: true,
      events: WEBHOOK_EVENTS,
    };
  }

  private extractQr(payload: unknown): string | null {
    if (!payload || typeof payload !== "object") return null;
    const p = payload as Record<string, unknown>;
    const candidates: unknown[] = [
      p.base64,
      (p.qrcode as { base64?: string } | undefined)?.base64,
      (p.qr as { base64?: string } | undefined)?.base64,
    ];
    for (const c of candidates) {
      if (typeof c !== "string" || c.length < 40) continue;
      // Baileys "code" (2@...) nao e imagem — so PNG/data-URL
      if (c.startsWith("2@")) continue;
      if (
        c.startsWith("data:image/") ||
        c.startsWith("iVBOR") ||
        c.startsWith("/9j/") ||
        /^[A-Za-z0-9+/=\r\n]+$/.test(c.slice(0, 80))
      ) {
        return c;
      }
    }
    return null;
  }

  /** Evolution muitas vezes nao devolve QR no create — busca via /instance/connect. */
  private async fetchQrWithRetry(
    instanceName: string,
    attempts = 5,
    delayMs = 1200,
  ): Promise<string | null> {
    for (let i = 0; i < attempts; i++) {
      try {
        const payload = await this.evolution.connect(instanceName);
        const qr = this.extractQr(payload);
        if (qr) {
          this.logger.log(
            `QR obtido via connect attempt ${i + 1}/${attempts} (${qr.length} chars)`,
          );
          return qr;
        }
        this.logger.debug(
          `connect attempt ${i + 1}/${attempts}: sem base64 keys=${Object.keys((payload as object) || {}).join(",")}`,
        );
      } catch (err) {
        this.logger.warn(
          `connect QR attempt ${i + 1}/${attempts} failed: ${err}`,
        );
        // Sem HTTP da Evolution, nao adianta martelar
        if (String(err).includes("network") || String(err).includes("abort")) {
          break;
        }
      }
      if (i < attempts - 1) await this.sleep(delayMs);
    }
    return null;
  }

  private async safeLogout(name: string) {
    try {
      await this.evolution.logout(name);
      this.logger.log(`logout ok: ${name}`);
    } catch (err) {
      this.logger.debug(`logout ignore: ${err}`);
    }
  }

  private async safeDelete(name: string) {
    try {
      await this.evolution.delete(name);
      this.logger.log(`delete ok: ${name}`);
    } catch (err) {
      this.logger.debug(`delete ignore: ${err}`);
    }
  }

  private async createFreshEvolution(name: string) {
    return this.evolution.createInstance({
      instanceName: name,
      qrcode: true,
      integration: "WHATSAPP-BAILEYS",
      webhook: this.webhookConfig(),
    });
  }

  /**
   * Sessao Baileys em close frequentemente devolve {count} sem QR.
   * 1) logout limpa credenciais e connect gera QR
   * 2) se falhar: delete + recreate
   */
  private async forceFreshQr(instanceName: string): Promise<string | null> {
    this.logger.warn(`forceFreshQr: logout+connect ${instanceName}`);
    await this.safeLogout(instanceName);
    await this.sleep(1500);
    let qr = await this.fetchQrWithRetry(instanceName, 6, 1500);
    if (qr) return qr;

    this.logger.warn(`forceFreshQr: delete+recreate ${instanceName}`);
    await this.safeLogout(instanceName);
    await this.safeDelete(instanceName);
    await this.sleep(2500);

    try {
      const created = await this.createFreshEvolution(instanceName);
      qr = this.extractQr(created) || this.extractQr(created.qrcode);
      if (qr) return qr;
    } catch (err) {
      this.logger.warn(`create after delete failed: ${err}`);
      // Instancia fantasma: espera e tenta delete+create de novo
      await this.sleep(2000);
      await this.safeDelete(instanceName);
      await this.sleep(2000);
      try {
        const created = await this.createFreshEvolution(instanceName);
        qr = this.extractQr(created) || this.extractQr(created.qrcode);
        if (qr) return qr;
      } catch (err2) {
        this.logger.error(`recreate retry failed: ${err2}`);
      }
    }

    return this.fetchQrWithRetry(instanceName, 8, 1500);
  }

  private async persistQr(
    rowId: string,
    qr: string | null,
    statusFallback: string,
  ) {
    const status = qr ? "qr" : statusFallback;
    await this.db.query(
      `UPDATE hubee_wpp.instances
       SET status = $2,
           last_qr_base64 = $3,
           phone_jid = CASE WHEN $2 = 'qr' THEN NULL ELSE phone_jid END,
           updated_at = now()
       WHERE id = $1`,
      [rowId, status, qr],
    );
  }

  async createOrConnect(usuarioId: string, force = false) {
    if (!usuarioId?.trim()) {
      throw new BadRequestException("usuarioId is required");
    }
    let row = await this.findByUsuario(usuarioId);

    if (!row) {
      const name = this.instanceNameForUsuario(usuarioId);
      let qr: string | null = null;
      try {
        const created = await this.createFreshEvolution(name);
        qr = this.extractQr(created) || this.extractQr(created.qrcode);
      } catch (err) {
        this.logger.warn(`createInstance failed, forceFresh: ${err}`);
      }
      if (!qr) qr = await this.fetchQrWithRetry(name);
      if (!qr) qr = await this.forceFreshQr(name);

      const status = qr ? "qr" : "connecting";
      const insert = await this.db.query(
        `INSERT INTO hubee_wpp.instances
           (hubee_usuario_id, evolution_instance_name, status, last_qr_base64)
         VALUES ($1, $2, $3, $4)
         ON CONFLICT (hubee_usuario_id) DO UPDATE SET
           evolution_instance_name = EXCLUDED.evolution_instance_name,
           status = EXCLUDED.status,
           last_qr_base64 = COALESCE(EXCLUDED.last_qr_base64, hubee_wpp.instances.last_qr_base64),
           updated_at = now()
         RETURNING id, hubee_usuario_id, evolution_instance_name, phone_jid, status,
                   last_qr_base64, created_at, updated_at`,
        [usuarioId, name, status, qr],
      );
      row = insert.rows[0];
      return { instance: row, qrcodeBase64: qr };
    }

    const name = row.evolution_instance_name as string;

    // Ja conectado
    try {
      const state = await this.evolution.connectionState(name);
      if (state.instance?.state === "open" && !force) {
        await this.db.query(
          `UPDATE hubee_wpp.instances
           SET status = 'open', last_qr_base64 = NULL, updated_at = now()
           WHERE id = $1`,
          [row.id],
        );
        row = await this.findByUsuario(usuarioId);
        return { instance: row, qrcodeBase64: null };
      }
    } catch {
      /* ignore */
    }

    let qr: string | null = null;

    if (force) {
      qr = await this.forceFreshQr(name);
    } else {
      try {
        const qrPayload = await this.evolution.connect(name);
        qr = this.extractQr(qrPayload);
        if (!qr) qr = await this.fetchQrWithRetry(name, 5, 1200);
      } catch (err) {
        this.logger.warn(`connect failed: ${err}`);
      }

      // close/connecting sem QR → sessão podre
      if (!qr) {
        let evoState = "unknown";
        try {
          const st = await this.evolution.connectionState(name);
          evoState = st.instance?.state || "unknown";
        } catch {
          /* ignore */
        }
        if (evoState !== "open") {
          qr = await this.forceFreshQr(name);
        }
      }
    }

    let mapped = qr ? "qr" : "connecting";
    if (!qr) {
      try {
        const st = await this.evolution.connectionState(name);
        const s = st.instance?.state;
        if (s === "open") mapped = "open";
        else if (s === "close") mapped = "close";
      } catch {
        /* ignore */
      }
    }

    await this.persistQr(row.id as string, qr, mapped);
    row = await this.findByUsuario(usuarioId);
    return {
      instance: row,
      qrcodeBase64: qr || (row?.last_qr_base64 as string) || null,
    };
  }

  async getQr(usuarioId: string, force = false) {
    const row = await this.findByUsuario(usuarioId);
    if (!row) throw new NotFoundException("Instance not found");
    const name = row.evolution_instance_name as string;

    try {
      const state = await this.evolution.connectionState(name);
      const st = state.instance?.state;
      if (st === "open" && !force) {
        await this.db.query(
          `UPDATE hubee_wpp.instances
           SET status = 'open', last_qr_base64 = NULL, updated_at = now()
           WHERE id = $1`,
          [row.id],
        );
        return {
          instance: { ...row, status: "open", last_qr_base64: null },
          qrcodeBase64: null,
          state: "open",
        };
      }
    } catch {
      /* ignore */
    }

    let qr: string | null = null;

    // Sempre pedir QR fresco a Evolution — last_qr_base64 no DB expira em segundos
    // e o poll com force=false reutilizava o mesmo payload (WhatsApp rejeita).
    if (force) {
      qr = await this.forceFreshQr(name);
    } else {
      qr = await this.fetchQrWithRetry(name, 5, 800);
      if (!qr) qr = await this.forceFreshQr(name);
    }

    if (qr) {
      await this.persistQr(row.id as string, qr, "qr");
      return {
        instance: { ...row, status: "qr", last_qr_base64: qr },
        qrcodeBase64: qr,
        state: "qr",
        fetchedAt: new Date().toISOString(),
      };
    }

    return {
      instance: row,
      qrcodeBase64: (row.last_qr_base64 as string) || null,
      state: row.status,
      fetchedAt: new Date().toISOString(),
    };
  }

  async disconnect(usuarioId: string, hardDelete = false) {
    const row = await this.findByUsuario(usuarioId);
    if (!row) throw new NotFoundException("Instance not found");
    const name = row.evolution_instance_name as string;
    await this.safeLogout(name);
    if (hardDelete) {
      await this.safeDelete(name);
      await this.db.query(`DELETE FROM hubee_wpp.instances WHERE id = $1`, [
        row.id,
      ]);
      return { deleted: true };
    }
    await this.db.query(
      `UPDATE hubee_wpp.instances
       SET status = 'disconnected', last_qr_base64 = NULL, phone_jid = NULL,
           updated_at = now()
       WHERE id = $1`,
      [row.id],
    );
    return { disconnected: true };
  }

  async updateFromWebhook(
    instanceName: string,
    patch: {
      status?: string;
      phoneJid?: string | null;
      qrBase64?: string | null;
    },
  ) {
    const sets: string[] = ["updated_at = now()"];
    const params: unknown[] = [instanceName];
    if (patch.status) {
      params.push(patch.status);
      sets.push(`status = $${params.length}`);
    }
    if (patch.phoneJid !== undefined) {
      params.push(patch.phoneJid);
      sets.push(`phone_jid = $${params.length}`);
    }
    if (patch.qrBase64 !== undefined) {
      params.push(patch.qrBase64);
      sets.push(`last_qr_base64 = $${params.length}`);
    }
    await this.db.query(
      `UPDATE hubee_wpp.instances SET ${sets.join(", ")}
       WHERE evolution_instance_name = $1`,
      params,
    );
  }
}
