import { exchangeClientForAccount } from "../exchange/index.js";
import { getById as getOrderById } from "../repos/orders.js";
import * as chatRepo from "../repos/chat.js";
import * as settings from "../repos/settings.js";
import { writeAudit } from "../audit/audit.js";
import { TelegramBot } from "./bot.js";
import type { OrderRow } from "../repos/orders.js";

/**
 * Ponte de chat com o Telegram. Modelo: grupo único; o bot posta a mensagem
 * do cliente com contexto e o operador RESPONDE (reply) à mensagem do bot —
 * o reply é reenviado ao chat da exchange.
 *
 * Liga/desliga e credenciais ficam em `app_settings` (token criptografado).
 */

const OFFSET_KEY = "telegram_update_offset";

export interface TelegramConfig {
  enabled: boolean;
  token: string | null;
  chatId: string | null;
}

export async function loadTelegramConfig(): Promise<TelegramConfig> {
  const [enabled, token, chatId] = await Promise.all([
    settings.getText(settings.TELEGRAM_ENABLED),
    settings.getSecret(settings.TELEGRAM_BOT_TOKEN),
    settings.getText(settings.TELEGRAM_CHAT_ID),
  ]);
  return { enabled: enabled === "1", token, chatId };
}

function getBot(cfg: TelegramConfig): TelegramBot | null {
  if (!cfg.enabled || !cfg.token || !cfg.chatId) return null;
  return new TelegramBot(cfg.token);
}

/**
 * Posta uma mensagem de cliente (direction='in') no grupo e guarda o
 * telegram_message_id no chat_messages para casar o reply depois.
 */
export async function postIncomingToTelegram(
  chatMessageId: number,
  order: OrderRow,
  customerName: string | null,
  content: string,
): Promise<void> {
  const cfg = await loadTelegramConfig();
  const bot = getBot(cfg);
  if (!bot || !cfg.chatId) return;

  const header =
    `💬 <b>Nova mensagem</b> — ordem <code>${order.order_no}</code>\n` +
    `Par: ${order.asset}/${order.fiat} • ${order.trade_type}` +
    (order.total_price ? ` • ${order.total_price} ${order.fiat}` : "") +
    (customerName ? `\nCliente: ${escapeHtml(customerName)}` : "") +
    `\n\n${escapeHtml(content)}\n\n<i>Responda a esta mensagem para enviar ao cliente.</i>`;

  const tgId = await bot.sendMessage(cfg.chatId, header);
  await chatRepo.setTelegramMessageId(chatMessageId, tgId);
}

/**
 * Lê os updates do Telegram e despacha os replies dos operadores de volta
 * ao chat da exchange. Mantém o offset em app_settings (idempotente).
 */
export async function pollAndDispatchReplies(): Promise<number> {
  const cfg = await loadTelegramConfig();
  const bot = getBot(cfg);
  if (!bot) return 0;

  const offsetText = await settings.getText(OFFSET_KEY);
  let offset = offsetText ? Number(offsetText) : 0;

  const updates = await bot.getUpdates(offset);
  let dispatched = 0;

  for (const u of updates) {
    offset = u.update_id + 1;
    const msg = u.message;
    const replyTo = msg?.reply_to_message?.message_id;
    if (!msg?.text || !replyTo) continue;

    const original = await chatRepo.findByTelegramMessageId(replyTo);
    if (!original || original.telegram_replied) continue;

    try {
      await dispatchReply(original.order_no, msg.text);
      await chatRepo.markTelegramReplied(original.id);
      dispatched++;
    } catch (err) {
      console.error(`[telegram] falha ao reenviar reply da ordem ${original.order_no}:`, errMsg(err));
    }
  }

  await settings.setText(OFFSET_KEY, String(offset));
  return dispatched;
}

/** Reenvia o texto para o chat da exchange da ordem (trata ordem fechada). */
async function dispatchReply(orderNo: string, text: string): Promise<void> {
  // Localiza a ordem mais recente com esse número p/ achar a conta.
  const row = await findOrderRowByNo(orderNo);
  if (!row) throw new Error(`Ordem ${orderNo} não encontrada localmente.`);

  const client = await exchangeClientForAccount(row.account_id);
  await client.sendChatMessage(orderNo, text);
  await chatRepo.insertOutgoing(orderNo, row.id, text, false);
  await writeAudit({
    action: "chat.telegram_reply",
    entity: "order",
    entityId: row.id,
    detail: { orderNo },
  });
}

async function findOrderRowByNo(orderNo: string): Promise<OrderRow | null> {
  // A mensagem guarda order_id quando disponível; senão buscamos por número.
  const byMsg = await chatRepo.listByOrder(orderNo);
  const withOrderId = byMsg.find((m) => m.order_id != null);
  if (withOrderId?.order_id) return getOrderById(withOrderId.order_id);
  return null;
}

function escapeHtml(s: string): string {
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function errMsg(err: unknown): string {
  return err instanceof Error ? err.message : String(err);
}
