import { createHash } from "crypto";
import { Injectable, Logger } from "@nestjs/common";
import { DbService } from "../db/db.service";

export type UpsertMessageInput = {
  instanceId: string;
  remoteJid: string;
  contactName?: string | null;
  waMessageId?: string | null;
  fromMe: boolean;
  body?: string | null;
  messageType?: string;
  sentAt: Date;
  rawJson?: unknown;
  source?: "evolution" | "txt_import";
};

@Injectable()
export class MessagesService {
  private readonly logger = new Logger(MessagesService.name);

  constructor(private readonly db: DbService) {}

  static evolutionDedupeKey(instanceId: string, waMessageId: string) {
    return `${instanceId}:${waMessageId}`;
  }

  static txtDedupeKey(
    instanceId: string,
    chatRemote: string,
    timestampIso: string,
    body: string,
  ) {
    const normalized = body.trim().replace(/\s+/g, " ").toLowerCase();
    const hash = createHash("sha256")
      .update(`${chatRemote}|${timestampIso}|${normalized}`)
      .digest("hex")
      .slice(0, 32);
    return `${instanceId}:txt:${hash}`;
  }

  async ensureChat(
    instanceId: string,
    remoteJid: string,
    contactName?: string | null,
    lastMessageAt?: Date,
  ): Promise<string> {
    const res = await this.db.query<{ id: string }>(
      `INSERT INTO hubee_wpp.chats (instance_id, remote_jid, contact_name, last_message_at)
       VALUES ($1, $2, $3, $4)
       ON CONFLICT (instance_id, remote_jid) DO UPDATE SET
         contact_name = COALESCE(EXCLUDED.contact_name, hubee_wpp.chats.contact_name),
         last_message_at = GREATEST(
           COALESCE(hubee_wpp.chats.last_message_at, '-infinity'::timestamptz),
           COALESCE(EXCLUDED.last_message_at, '-infinity'::timestamptz)
         ),
         updated_at = now()
       RETURNING id`,
      [instanceId, remoteJid, contactName ?? null, lastMessageAt ?? null],
    );
    return res.rows[0].id;
  }

  async upsertMessage(input: UpsertMessageInput): Promise<"inserted" | "skipped"> {
    const waId = input.waMessageId || null;
    const dedupeKey =
      input.source === "txt_import"
        ? MessagesService.txtDedupeKey(
            input.instanceId,
            input.remoteJid,
            input.sentAt.toISOString(),
            input.body || "",
          )
        : waId
          ? MessagesService.evolutionDedupeKey(input.instanceId, waId)
          : MessagesService.txtDedupeKey(
              input.instanceId,
              input.remoteJid,
              input.sentAt.toISOString(),
              input.body || "",
            );

    const chatId = await this.ensureChat(
      input.instanceId,
      input.remoteJid,
      input.contactName,
      input.sentAt,
    );

    const direction = input.fromMe ? "out" : "in";
    const res = await this.db.query(
      `INSERT INTO hubee_wpp.messages (
         instance_id, chat_id, wa_message_id, from_me, direction,
         body, message_type, sent_at, raw_json, source, dedupe_key
       ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10,$11)
       ON CONFLICT (dedupe_key) DO NOTHING
       RETURNING id`,
      [
        input.instanceId,
        chatId,
        waId,
        input.fromMe,
        direction,
        input.body ?? null,
        input.messageType || "conversation",
        input.sentAt.toISOString(),
        JSON.stringify(input.rawJson ?? null),
        input.source || "evolution",
        dedupeKey,
      ],
    );
    return res.rowCount && res.rowCount > 0 ? "inserted" : "skipped";
  }

  async listMessages(opts: {
    usuarioId?: string;
    scopeAll?: boolean;
    chatId?: string;
    from?: string;
    to?: string;
    limit?: number;
    offset?: number;
  }) {
    const limit = Math.min(opts.limit ?? 100, 500);
    const offset = opts.offset ?? 0;
    const params: unknown[] = [];
    const where: string[] = [];

    if (!opts.scopeAll) {
      if (!opts.usuarioId) {
        return { items: [], total: 0 };
      }
      params.push(opts.usuarioId);
      where.push(`i.hubee_usuario_id = $${params.length}`);
    } else if (opts.usuarioId) {
      params.push(opts.usuarioId);
      where.push(`i.hubee_usuario_id = $${params.length}`);
    }

    if (opts.chatId) {
      params.push(opts.chatId);
      where.push(`m.chat_id = $${params.length}`);
    }
    if (opts.from) {
      params.push(opts.from);
      where.push(`m.sent_at >= $${params.length}::timestamptz`);
    }
    if (opts.to) {
      params.push(opts.to);
      where.push(`m.sent_at <= $${params.length}::timestamptz`);
    }

    const whereSql = where.length ? `WHERE ${where.join(" AND ")}` : "";
    const countRes = await this.db.query<{ count: string }>(
      `SELECT count(*)::text AS count
       FROM hubee_wpp.messages m
       JOIN hubee_wpp.instances i ON i.id = m.instance_id
       ${whereSql}`,
      params,
    );

    params.push(limit);
    params.push(offset);
    const listRes = await this.db.query(
      `SELECT m.id, m.instance_id, m.chat_id, m.wa_message_id, m.from_me, m.direction,
              m.body, m.message_type, m.sent_at, m.source,
              i.hubee_usuario_id, c.remote_jid, c.contact_name
       FROM hubee_wpp.messages m
       JOIN hubee_wpp.instances i ON i.id = m.instance_id
       JOIN hubee_wpp.chats c ON c.id = m.chat_id
       ${whereSql}
       ORDER BY m.sent_at DESC
       LIMIT $${params.length - 1} OFFSET $${params.length}`,
      params,
    );

    return {
      items: listRes.rows,
      total: parseInt(countRes.rows[0]?.count || "0", 10),
      limit,
      offset,
    };
  }

  async listChats(opts: { usuarioId?: string; scopeAll?: boolean }) {
    const params: unknown[] = [];
    const where: string[] = [];
    if (!opts.scopeAll) {
      if (!opts.usuarioId) return [];
      params.push(opts.usuarioId);
      where.push(`i.hubee_usuario_id = $${params.length}`);
    } else if (opts.usuarioId) {
      params.push(opts.usuarioId);
      where.push(`i.hubee_usuario_id = $${params.length}`);
    }
    const whereSql = where.length ? `WHERE ${where.join(" AND ")}` : "";
    const res = await this.db.query(
      `SELECT c.id, c.instance_id, c.remote_jid, c.contact_name, c.last_message_at,
              i.hubee_usuario_id
       FROM hubee_wpp.chats c
       JOIN hubee_wpp.instances i ON i.id = c.instance_id
       ${whereSql}
       ORDER BY c.last_message_at DESC NULLS LAST`,
      params,
    );
    return res.rows;
  }

  extractBody(message: Record<string, unknown> | undefined): string | null {
    if (!message) return null;
    if (typeof message.conversation === "string") return message.conversation;
    const ext = message.extendedTextMessage as { text?: string } | undefined;
    if (ext?.text) return ext.text;
    const img = message.imageMessage as { caption?: string } | undefined;
    if (img?.caption) return img.caption;
    const vid = message.videoMessage as { caption?: string } | undefined;
    if (vid?.caption) return vid.caption;
    if (message.imageMessage) return "[image]";
    if (message.videoMessage) return "[video]";
    if (message.audioMessage) return "[audio]";
    if (message.documentMessage) return "[document]";
    if (message.stickerMessage) return "[sticker]";
    return null;
  }

  normalizeEvolutionMessage(
    instanceId: string,
    raw: Record<string, unknown>,
  ): UpsertMessageInput | null {
    const key = (raw.key || {}) as {
      id?: string;
      remoteJid?: string;
      fromMe?: boolean;
      participant?: string;
    };
    const remoteJid = key.remoteJid;
    if (!remoteJid) return null;
    const message = raw.message as Record<string, unknown> | undefined;
    const ts =
      typeof raw.messageTimestamp === "number"
        ? raw.messageTimestamp
        : typeof raw.messageTimestamp === "string"
          ? parseInt(raw.messageTimestamp, 10)
          : Date.now() / 1000;
    const sentAt = new Date(ts > 1e12 ? ts : ts * 1000);
    const pushName =
      typeof raw.pushName === "string" ? raw.pushName : undefined;
    return {
      instanceId,
      remoteJid,
      contactName: pushName,
      waMessageId: key.id || null,
      fromMe: !!key.fromMe,
      body: this.extractBody(message),
      messageType:
        typeof raw.messageType === "string"
          ? raw.messageType
          : "conversation",
      sentAt,
      rawJson: raw,
      source: "evolution",
    };
  }

  async ingestEvolutionPayload(
    instanceName: string,
    event: string,
    data: unknown,
  ): Promise<{ inserted: number; skipped: number }> {
    const inst = await this.db.query<{ id: string }>(
      `SELECT id FROM hubee_wpp.instances WHERE evolution_instance_name = $1 LIMIT 1`,
      [instanceName],
    );
    if (!inst.rows[0]) {
      this.logger.warn(`No hubee_wpp instance for Evolution name ${instanceName}`);
      return { inserted: 0, skipped: 0 };
    }
    const instanceId = inst.rows[0].id;
    const msgs: Record<string, unknown>[] = Array.isArray(data)
      ? (data as Record<string, unknown>[])
      : data && typeof data === "object" && Array.isArray((data as { messages?: unknown }).messages)
        ? ((data as { messages: Record<string, unknown>[] }).messages)
        : data && typeof data === "object"
          ? [data as Record<string, unknown>]
          : [];

    let inserted = 0;
    let skipped = 0;
    for (const raw of msgs) {
      const input = this.normalizeEvolutionMessage(instanceId, raw);
      if (!input) {
        skipped++;
        continue;
      }
      if (event === "SEND_MESSAGE" || event === "send.message") {
        input.fromMe = true;
      }
      const r = await this.upsertMessage(input);
      if (r === "inserted") inserted++;
      else skipped++;
    }
    return { inserted, skipped };
  }
}
