import { requireSupportWrite } from "@/lib/impersonate/support";
/**
 * POST /api/v1/conversations/[id]/reactivate-bot
 *
 * Devolve o atendimento ao agente. A REGRA vive em
 * `lib/escalacao/retomada.ts` — esta rota só resolve autenticação, org e a
 * tradução do resultado para HTTP.
 *
 * Ela foi extraída (Decisão 4 do briefing IA 360: a tool é fachada fina sobre a
 * regra que já existe) porque a mesma devolução precisa acontecer por dois
 * caminhos — a pessoa clicando aqui e a capacidade exposta ao agente. Regra
 * duplicada nos dois lados faria o sistema mentir para um deles.
 *
 * Ao extrair, apareceu o defeito: esta rota limpava só `bot_silenced_until` e
 * respondia `{ reactivated: true }` com o agente ainda travado por
 * `contacts.force_human` e por `assignee_kind='user'`. O conserto está na função;
 * o histórico do defeito, no cabeçalho dela.
 *
 * Auth: cookie session, role >= agent. Audit: `ai.reactivated_by_agent`.
 */
import { randomUUID } from "node:crypto";
import type { NextRequest } from "next/server";

import { ok, fail } from "@/lib/api/wrappers";
import { requireRole } from "@/lib/auth/require-role";
import { roleAtLeast } from "@/lib/auth/types";
import { devolverAtendimentoAoAgente } from "@/lib/escalacao/retomada";
import { createClient } from "@/lib/supabase/server";
import { traduzir } from "@/lib/i18n/dicionario";

export const dynamic = "force-dynamic";

interface RouteCtx {
  params: Promise<{ id: string }>;
}

export async function POST(_req: NextRequest, ctx: RouteCtx): Promise<Response> {
  const supportDenied = await requireSupportWrite();
  if (supportDenied) return supportDenied;

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

  const authz = await requireRole("agent", { requestId, resource: "conversations" });
  if (!authz.ok) return authz.response;
  const t = (texto: string) => traduzir(texto, authz.user.idioma);
  const { user: authUser, org: activeOrg } = authz;

  const supabase = await createClient();

  // #3436 — conversa que tem dono e o dono NÃO é quem pediu só volta ao agente
  // pela mão de um `manager` ou acima (ou de quem a assumir antes: `claim`).
  // A regra mora AQUI, e não em `devolverAtendimentoAoAgente`, porque o cron
  // `handoff-devolucao` chama a mesma função SEM sessão — uma regra de dono lá
  // dentro recusaria a devolução automática que o prazo venceu.
  //
  // A leitura é escopada por `organization_id` da sessão (nunca só por `id`):
  // conversa de outro tenant não é "conversa de outro atendente", é inexistente
  // — e quem decide isso é o filtro, não o `dono == null` que sobra dele.
  const { data: donaDaConversa, error: erroDoDono } = await supabase
    .from("conversations")
    .select("assigned_to_user_id")
    .eq("id", id)
    .eq("organization_id", activeOrg.orgId)
    .maybeSingle();
  if (erroDoDono) {
    return fail("internal_error", t("Erro ao validar conversa."), 500, { requestId });
  }
  if (!donaDaConversa) {
    return fail("not_found", t("Conversa não encontrada."), 404, { requestId });
  }
  const dono =
    (donaDaConversa as { assigned_to_user_id: string | null } | null)?.assigned_to_user_id ?? null;
  if (dono !== null && dono !== authUser.id && !roleAtLeast(activeOrg.role, "manager")) {
    // Sem tocar no silêncio, no dono nem na autorização do contato: a recusa
    // acontece antes de qualquer escrita, então a conversa do colega fica
    // exatamente como estava.
    return fail(
      "forbidden",
      t("Esta conversa é de outro atendente. Peça a um gerente ou assuma a conversa."),
      403,
      { requestId },
    );
  }

  const resultado = await devolverAtendimentoAoAgente(
    {
      supabase,
      organizationId: activeOrg.orgId,
      actor: { type: "user", id: authUser.id, role: activeOrg.role },
      requestId,
    },
    { conversationId: id, expectedAssignee: dono },
  );

  if (!resultado.ok) {
    if (resultado.erro === "conversation_not_found") {
      return fail("not_found", t("Conversa não encontrada."), 404, { requestId });
    }
    if (resultado.erro === "assignment_conflict") {
      return fail(
        "state_conflict",
        t("Alguém assumiu esta conversa agora — recarregue e tente de novo."),
        409,
        { requestId },
      );
    }
    // #2379: a marca permanente recusou ANTES de qualquer escrita — nada foi
    // devolvido, então a mensagem genérica abaixo ("Atendimento devolvido…")
    // mentiria e convidaria a repetir um gesto que vai recusar de novo.
    if (resultado.erro === "contato_sempre_humano") {
      return fail(
        "state_conflict",
        t("Este contato está marcado como sempre atendimento humano. Para a IA voltar a atendê-lo, remova a marca na ficha do contato."),
        409,
        { requestId },
      );
    }
    if (resultado.erro === "contact_state_unreadable") {
      return fail(
        "internal_error",
        t("Não foi possível conferir o contato desta conversa — nada foi alterado. Tente de novo."),
        500,
        { requestId },
      );
    }
    // A devolução em si já aconteceu; o que falhou foi o sinal que retoma um
    // acompanhamento pausado. Repetir a chamada é seguro (tudo é idempotente).
    return fail(
      "internal_error",
      t("Atendimento devolvido, mas o sinal de retomada do acompanhamento falhou — tente de novo."),
      500,
      { requestId },
    );
  }

  return ok(
    {
      reactivated: true,
      // O que a pessoa fez vai junto: quem chamou consegue mostrar na tela que o
      // agente não voltou cego.
      continuidade: {
        houve_atendimento_humano: resultado.continuidade.houveAtendimentoHumano,
        resumo: resultado.continuidade.resumo,
        decisoes: resultado.continuidade.decisoes.length,
        notas: resultado.continuidade.notas.length,
      },
    },
    { requestId },
  );
}
