import { requireSupportWrite } from "@/lib/impersonate/support";
/**
 * POST /api/v1/channel-sessions/[id]/reconnect — reconecta um canal caído.
 *
 * Dois modos, porque "caiu" tem duas causas com custos bem diferentes:
 *
 *  - PADRÃO (stop + start): soluço de rede, container reiniciado, sessão que
 *    parou sozinha. As credenciais em `/app/.sessions` continuam válidas e o
 *    engine volta sozinho para WORKING — sem QR, sem incomodar o usuário.
 *  - `{ force: true }` (stop + LOGOUT + start): o aparelho foi desvinculado
 *    pelo celular e o WhatsApp revogou a credencial. Aí o start comum
 *    reaproveita uma credencial morta e a sessão vai direto para FAILED, sem
 *    NUNCA passar por SCAN_QR_CODE — era exatamente esse o buraco em que a tela
 *    ficava presa esperando um QR que nunca vinha. O logout descarta a
 *    credencial e o pareamento recomeça do zero.
 *
 * O padrão é o modo suave de propósito: forçar logout sempre custaria um
 * reescaneamento a cada queda passageira. A UI só oferece o `force` depois que
 * o modo suave falhou.
 *
 * Canal EXCLUÍDO (arquivado) é recusado, não reconectado: subir a sessão de novo
 * no transporte devolveria um canal que recebe e não entrega nada — o webhook, o
 * ingest e o envio filtram `archived_at` e descartariam tudo. Vivo e surdo é pior
 * que desligado. E não há o que "reconectar": a exclusão deslogou o aparelho e
 * apagou a sessão no transporte, então o caminho de volta é conectar um número
 * (que também é o que a mensagem de erro diz).
 *
 * Canal OFICIAL é recusado por outro motivo, e com outro desfecho (422): ele não
 * tem sessão no transporte para parar e subir — `waha_session_name` é NULL nele
 * por CHECK. Reiniciar não é a operação dele; trocar a credencial é.
 *
 * Admin only. organization_id vem da sessão — nunca do path/body.
 */
import { randomUUID } from "node:crypto";
import type { NextRequest } from "next/server";
import { z } from "zod";

import { assertWahaConnectionIdle, ChannelConnectionError, renomearSessaoParaOTeto } from "@/lib/channels/connect-waha";
import { lerGuardarHistorico } from "@/lib/channels/acervo-do-historico";
import { nomeDaSessaoCabeNoWaha, podeRenomearSessaoDoWaha } from "@/lib/channels/nome-da-sessao";
import { createAdminClient } from "@/lib/supabase/admin";
import { sincronizarRecebimentoDeGrupos } from "@/lib/grupos/sincronizar-filtro";
import { mfaEmDivida } from "@/lib/auth/server";
import { audit } from "@/lib/audit";
import { ok, fail } from "@/lib/api/wrappers";
import { requireRole } from "@/lib/auth/require-role";
import { ARCHIVED_AT, queryTolerantToMissingArchived } from "@/lib/channels/archived";
import { createClient } from "@/lib/supabase/server";
import { getWahaClient, wahaFriendlyError } from "@/lib/waha/client";
import { traduzir } from "@/lib/i18n/dicionario";
import { traduzirLimiteDoPlano } from "@/lib/cobranca/limites";

export const dynamic = "force-dynamic";

const reconnectSchema = z.object({ force: z.boolean().optional() });

export async function POST(
  req: NextRequest,
  { params }: { params: Promise<{ id: string }> },
): Promise<Response> {
  const supportDenied = await requireSupportWrite();
  if (supportDenied) return supportDenied;

  const requestId = randomUUID();
  const { id } = await params;

  let rawBody: unknown = {};
  try {
    rawBody = await req.json();
  } catch {
    rawBody = {};
  }
  const parsedBody = reconnectSchema.safeParse(rawBody ?? {});
  const force = parsedBody.success ? (parsedBody.data.force ?? false) : false;

  const authz = await requireRole("admin", {
    requestId,
    resource: "channel_sessions",
    allowPlatformAdmin: true,
  });
  if (!authz.ok) return authz.response;
  const t = (texto: string) => traduzir(texto, authz.user.idioma);
  const { user, org: activeOrg } = authz;
  if (await mfaEmDivida()) return fail("mfa_required", "Confirme a verificação em duas etapas.", 403, { requestId });

  const supabase = await createClient();
  const buscar = (colunas: string) =>
    supabase
      .from("channel_sessions")
      .select(colunas)
      .eq("organization_id", activeOrg.orgId)
      .eq("id", id)
      .maybeSingle();
  // Tolerante à coluna ausente: num clone sem a migration 0106 nada está
  // arquivado, e exigir a coluna aqui derrubaria a reconexão inteira — que é o
  // socorro de quem está com o número fora do ar.
  const { data: sessionRaw } = await queryTolerantToMissingArchived(
    () => buscar(`id, waha_session_name, status, phone_number, metadata, ${ARCHIVED_AT}`),
    () => buscar("id, waha_session_name, status, phone_number, metadata"),
  );
  const session = sessionRaw as {
    id: string;
    waha_session_name: string | null;
    status?: string | null;
    phone_number?: string | null;
    archived_at?: string | null;
    metadata?: Record<string, unknown> | null;
  } | null;
  if (!session) return fail("not_found", t("Canal não encontrado."), 404, { requestId });
  if (session.archived_at) {
    return fail(
      "channel_archived",
      t("Este número foi excluído da Central de Conexões — reconectar não o traz de volta. Conecte um número para voltar a atender."),
      409,
      { requestId },
    );
  }
  // O nome da sessão é NULL no canal oficial, e o CHECK
  // `channel_sessions_provider_ref_check` garante que só nele. Afirmar `string`
  // aqui (era um cast) não fazia o valor existir: mandava `null` para o
  // transporte, que pedia `/api/sessions/null/stop` e devolvia erro de serviço —
  // culpando o WhatsApp por uma pergunta que nunca fez sentido.
  const nomeSessao = session.waha_session_name;
  if (!nomeSessao) {
    return fail(
      "channel_without_session",
      t("Este canal é o oficial (API da plataforma): ele não tem sessão de WhatsApp para reiniciar. Se parou de entregar, atualize a credencial na tela do canal oficial."),
      422,
      { requestId },
    );
  }

  const waha = getWahaClient();
  if (!waha) {
    return fail(
      "waha_not_configured",
      t("O WhatsApp (WAHA) não está configurado neste ambiente: faltam WAHA_API_BASE_URL e/ou WAHA_API_KEY. Configure-as e tente de novo."),
      503,
      { requestId },
    );
  }

  // Reconectar com um nome fora do teto do WAHA é pedir 400 três vezes seguidas
  // (stop, logout, start). Mesma fronteira do caminho de conectar: cura quem a
  // 0232 curaria, recusa o resto. Aqui o `status` vale de verdade — esta linha
  // veio da tabela, não da reserva, que sobrescreve o status com `STARTING`.
  let nomeParaOTransporte = nomeSessao;
  if (!nomeDaSessaoCabeNoWaha(nomeSessao)) {
    if (!podeRenomearSessaoDoWaha(session)) {
      return fail(
        "connection_session_name_too_long",
        t("O identificador desta conexão passou do limite que o WhatsApp aceita e não pode ser trocado sem desligar o número. Fale com o suporte antes de reconectar."),
        409,
        { requestId },
      );
    }
    try {
      nomeParaOTransporte = await renomearSessaoParaOTeto(createAdminClient(), {
        id: session.id, organization_id: activeOrg.orgId, waha_session_name: nomeSessao,
      });
    } catch {
      return fail(
        "connection_session_name_too_long",
        t("O identificador desta conexão passou do limite que o WhatsApp aceita e não pôde ser corrigido agora. Tente novamente em instantes."),
        409,
        { requestId },
      );
    }
  }

  try {
    await assertWahaConnectionIdle(createAdminClient(), activeOrg.orgId, id);
    // A linha morta e sem número não ocupa vaga do plano (0809); revivê-la volta
    // a ocupar. A vaga é conferida e tomada no banco (9036) ANTES de tocar o
    // transporte — número já pareado passa direto, mesmo acima do teto.
    const vaga = await createAdminClient().rpc("fn_vaga_ao_reviver_canal", { p_org: activeOrg.orgId, p_canal: id });
    if (vaga.error) {
      const limite = traduzirLimiteDoPlano(vaga.error, authz.user.idioma);
      if (limite) return fail(limite.code, limite.message, 409, { requestId, details: limite.details });
      return fail("internal_error", t("Não foi possível concluir a conexão. Tente novamente."), 500, { requestId });
    }
    await waha.stopSession(nomeParaOTransporte);
    // Só no modo forçado: descartar a credencial é irreversível — obriga a
    // reescanear o QR mesmo que ela ainda estivesse boa.
    if (force) await waha.logoutSession(nomeParaOTransporte);
    // Com a opção de acervo ligada nesta conexão, o start também converge o
    // store (num número já pareado, guarda daqui em diante). Desligada, a
    // chamada é a de sempre, sem segundo argumento — e o store fica como o
    // canal o tem: só o PATCH /acervo desliga.
    const opcoesAcervo = lerGuardarHistorico(session.metadata) ? { guardarHistorico: true } : undefined;
    const remote = (await (opcoesAcervo
      ? waha.startSession(nomeParaOTransporte, opcoesAcervo)
      : waha.startSession(nomeParaOTransporte))) as { status?: string };
    const nextStatus = remote.status ?? "STARTING";
    const patch = { status: nextStatus, status_reason: null, last_status_change_at: new Date().toISOString(), consecutive_health_fails: 0 };
    const { error: syncError } = await supabase.from("channel_sessions").update(patch).eq("organization_id", activeOrg.orgId).eq("id", id);

    if (syncError) throw new Error("connection_sync_failed");

    // A sessão recriada (volume do WAHA perdido, logout forçado) nasce ignorando
    // grupos, mas o banco pode ter grupos LIGADOS neste número. Ressincroniza o
    // filtro — sem PUT quando já está certo, e nunca lança.
    await sincronizarRecebimentoDeGrupos(
      createAdminClient(),
      (ref, receber) => waha.definirRecebimentoDeGrupos(ref, receber),
      { organizationId: activeOrg.orgId, channelSessionId: id, sessionRef: nomeParaOTransporte },
    );

    void audit({
      action: "channel.reconnected",
      actorUserId: user.id,
      organizationId: activeOrg.orgId,
      resourceType: "channel_session",
      resourceId: id,
      requestId,
      metadata: { waha_session_name: nomeParaOTransporte, force },
    });

    return ok({ id, status: nextStatus, force }, { requestId });
  } catch (err) {
    if (err instanceof ChannelConnectionError) return fail(err.code, "Uma conexão está em andamento. Aguarde e tente novamente.", err.status, { requestId });
    await supabase.from("channel_sessions").update({ status: "FAILED", status_reason: "connection_repair_required", last_status_change_at: new Date().toISOString() })
      .eq("organization_id", activeOrg.orgId).eq("id", id);
    return fail("waha_error", wahaFriendlyError(err), 502, { requestId });
  }
}
