import { requireSupportWrite } from "@/lib/impersonate/support";
/**
 * POST /api/v1/pipelines/[id]/stages — cria uma etapa no fim do funil.
 *
 * Até a tela de etapas existir, NENHUMA superfície criava etapa: o gatilho
 * `trg_seed_default_pipeline_for_org` semeia um funil de e-commerce em toda
 * organização nova, então uma clínica abre o sistema e vê "Carrinho abandonado"
 * sem ter como corrigir.
 *
 * ⚠️ AQUI SÓ HÁ TRANSPORTE. A operação inteira vive em
 * `lib/leads/stage-operations.ts` porque o agente de IA também organiza o funil
 * (BRIEFING §3, Decisão 4): duas superfícies escrevendo na mesma tabela por
 * caminhos diferentes fariam o sistema mentir para uma das duas.
 *
 * Auth: sessão por cookie, papel manager+ (é configuração do funil, não trabalho
 * de card). `organization_id` sai do JWT — nunca do body. O client é o do
 * usuário, então a RLS vale; o filtro explícito é a convenção do repo e a rede
 * que sobra se a policy mudar.
 */
import { randomUUID } from "node:crypto";
import { type NextRequest } from "next/server";
import { z } from "zod";

import { respostaDeRecusa } from "@/lib/api/recusa";
import { fail, ok } from "@/lib/api/wrappers";
import { requireRole } from "@/lib/auth/require-role";
import { criarEtapa } from "@/lib/leads/stage-operations";
import { anexaFluxos, carregaFluxosPorEtapa } from "@/lib/followup/fluxos-da-etapa";
import { createClient } from "@/lib/supabase/server";
import { traduzir } from "@/lib/i18n/dicionario";

export const dynamic = "force-dynamic";

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

// `.max(80)`: o nome é o topo de uma coluna do quadro, não um parágrafo. O banco
// não limita, mas a tela quebra muito antes disso.
//
// `expected_duration_hours` é opcional e aceita `null`: uma etapa nasce sem
// janela configurada e o radar cai no padrão de 24 h/72 h. 1 a 8760 inteiro
// (uma hora a um ano) — a coluna é `numeric` sem CHECK, então este Zod é a
// primeira rede (a migration com CHECK ficou fora deste escopo, #1532).
const bodySchema = z
  .object({
    name: z.string().min(1).max(80),
    expected_duration_hours: z.number().int().min(1).max(8760).nullable().optional(),
  })
  .strict();

/**
 * GET — as etapas vivas do funil, na ordem do quadro.
 *
 * Existia criação de etapa e nenhuma LEITURA: quem precisava oferecer "escolha
 * a etapa" numa tela (a campanha, agora) não tinha de onde tirar a lista, e a
 * saída seria cada tela consultar o banco por conta própria — duas réguas para
 * "quais etapas existem", que divergem no primeiro arquivamento.
 *
 * Ganho e perda entram: quem escolhe etapa para FILTRAR público quer poder
 * dizer "quem está em Perdido". Quem escolhe etapa de NASCIMENTO é recusado
 * antes, pela regra de que card não nasce fechado.
 */
export async function GET(_req: NextRequest, ctx: RouteCtx): Promise<Response> {
  const requestId = randomUUID();
  const authz = await requireRole("manager", { requestId, resource: "pipeline_stages" });
  if (!authz.ok) return authz.response;
  const t = (texto: string) => traduzir(texto, authz.user.idioma);
  const { id } = await ctx.params;

  const supabase = await createClient();
  const { data, error } = await supabase
    .from("crm_stages")
    // A janela de esfriando, a chance de fechamento e o aviso de equipe entram
    // na leitura como as demais colunas de configuração: quem precisa "escolha
    // a etapa" também precisa saber quanto tempo cada uma leva, qual a
    // probabilidade calibrada pelo gestor e se a etapa abre aviso na Central —
    // e sem elas aqui, a tela de configurações do funil mostra os campos vazios
    // logo depois de salvar (issue #2999).
    .select(
      "id, name, position, is_won, is_lost, expected_duration_hours, win_probability, avisar_na_central",
    )
    .eq("organization_id", authz.org.orgId)
    .eq("pipeline_id", id)
    .eq("is_archived", false)
    .order("position", { ascending: true });
  if (error) return fail("internal_error", t("Falha ao listar etapas."), 500, { requestId });

  // #3603 — a lista read-only de «quais fluxos disparam ao entrar nesta etapa».
  // Nenhuma consulta por etapa, e as MESMAS portas do motor
  // (`lib/followup/fluxos-da-etapa.ts`). Se esta leitura falhar, a resposta sai
  // SEM o campo — a tela então não afirma nem "tem" nem "não tem" —, em vez de
  // derrubar uma listagem de etapas que já foi lida com sucesso.
  let fluxos: Awaited<ReturnType<typeof carregaFluxosPorEtapa>> | null = null;
  try {
    fluxos = await carregaFluxosPorEtapa(supabase, authz.org.orgId);
  } catch {
    fluxos = null;
  }

  return ok(anexaFluxos((data ?? []) as Array<{ id: string }>, fluxos), { requestId });
}

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

  const requestId = randomUUID();
  const authz = await requireRole("manager", { requestId, resource: "crm_stages" });
  if (!authz.ok) return authz.response;
  const t = (texto: string) => traduzir(texto, authz.user.idioma);

  const { id: pipelineId } = await ctx.params;

  let json: unknown;
  try {
    json = await req.json();
  } catch {
    return fail("invalid_request", t("Corpo não é JSON válido."), 400, { requestId });
  }

  const parsed = bodySchema.safeParse(json);
  if (!parsed.success) {
    return fail("unprocessable_entity", t("Dê um nome à etapa — é o que aparece no topo da coluna."), 422, {
      requestId,
      details: parsed.error.flatten(),
    });
  }

  const supabase = await createClient();
  try {
    const { funil } = await criarEtapa(
      {
        supabase,
        organizationId: authz.org.orgId,
        actor: { type: "user", id: authz.user.id, role: authz.org.role },
        requestId,
      },
      {
        pipelineId,
        nome: parsed.data.name,
        // Só quando veio pedido: a chave ausente é o comportamento de hoje.
        ...(parsed.data.expected_duration_hours !== undefined
          ? { expected_duration_hours: parsed.data.expected_duration_hours }
          : {}),
      },
    );
    return ok(funil, { status: 201, requestId });
  } catch (err) {
    return respostaDeRecusa(err, requestId);
  }
}
