"use server";

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { z } from "zod";

import { invalidarUsuario } from "@/lib/auth/cache-curto";
import { requireAuth, resolveActiveOrg } from "@/lib/auth/server";
import { createClient } from "@/lib/supabase/server";
import { ensureTenantForUser } from "@/lib/auth/provision";
import { decidirConviteDoSignup } from "@/lib/auth/convite-no-signup";
import { modoDeCadastro } from "@/lib/auth/politica-de-cadastro";
import { createRegistrationRequest, estadoDoPedido } from "@/lib/auth/registration-requests";
import { acessoFoiRevogado } from "@/lib/auth/vinculo-revogado";
import { organizationNameSchema } from "@/lib/auth/schemas";
import { audit } from "@/lib/audit";
import { authRateLimited, AUTH_LIMITS } from "@/lib/auth/rate-limit";

export type RecoverOrganizationResult =
  | { ok: true }
  | {
      ok: false;
      error:
        | "validation_error"
        | "rate_limited"
        | "invite_pending"
        | "somente_convite"
        | "provision_failed"
        | "access_revoked"
        | "pedido_recusado"
        | "plano_obrigatorio";
    };

/**
 * A SEGUNDA CHANCE DO PRIMEIRO ACESSO.
 *
 * ─── O beco sem saída ───────────────────────────────────────────────────────
 *
 * `app/auth/confirm/route.ts:132-142` estabelece a SESSÃO e só depois tenta
 * provisionar. Se `ensureTenantForUser` levanta — banco fora do ar por um
 * segundo, `service_role` ainda não propagada, disco cheio —, a rota audita e
 * manda para `/login?error=provisionamento`. Mas a sessão já existe: a pessoa
 * está logada, sem organização, e daí em diante todo caminho fecha.
 *
 *   /login       → entra de novo → `signInWithPassword.ts:110` → /app/inbox
 *   /app/inbox   → "Você não tem nenhuma organização ativa. Aceite um convite
 *                  ou contate o admin."
 *   /onboarding  → `layout.tsx:15` sem org → redirect("/login")
 *
 * As duas saídas que a mensagem oferece não existem para quem instalou o
 * sistema: não há convite (ninguém convidou) e não há admin (ELE é o admin).
 * Num self-host, a conta bloqueada é a do dono, e o desbloqueio pedia SQL na
 * mão — numa instalação que a pessoa acabou de fazer para não precisar disso.
 *
 * ─── O que esta action pode e o que ela não pode ────────────────────────────
 *
 * Só o NOME vem do cliente, e passa por Zod. `organization_id`, papel e vínculo
 * nunca são aceitos de fora: quem os decide é `ensureTenantForUser`, o mesmo
 * caminho do signup. Quem já tem organização é redirecionado antes de qualquer
 * escrita — sem isso, a tela viraria "crie quantas empresas quiser".
 *
 * O convite é classificado ANTES do contador: quem foi convidado não abre
 * empresa própria (é a mesma bifurcação de `confirm/route.ts`), e chegar aqui
 * com convite pendente não pode gastar tentativa de quem realmente precisa.
 *
 * Achado de @prevprocesso-maker no PR #465.
 */
/**
 * `planoId` (#2846): o plano escolhido nesta tela. É por aqui que sai do beco
 * quem chegou sem plano — conta nova pelo Google no /login, ou cadastro feito
 * antes da cobrança — numa instalação que oferece planos sem ter um padrão:
 * sem escolha, o gatilho recusa a empresa com `plano_obrigatorio`. Quem valida
 * a escolha é o gatilho (oferecido e não arquivado; senão, o padrão).
 */
export async function recoverOrganization(name: string, planoId?: string): Promise<RecoverOrganizationResult> {
  const parsed = organizationNameSchema.safeParse(name);
  if (!parsed.success) return { ok: false, error: "validation_error" };
  const plano = z.string().uuid().optional().safeParse(planoId || undefined);
  if (!plano.success) return { ok: false, error: "validation_error" };
  const comPlano = plano.data ? { cobranca_plan_id: plano.data } : {};

  const user = await requireAuth();
  const activeOrg = await resolveActiveOrg(user);
  if (activeOrg) redirect("/app");

  const supabase = await createClient();
  const {
    data: { user: authUser },
  } = await supabase.auth.getUser();
  if (!authUser) return { ok: false, error: "provision_failed" };

  // QUEM PERDEU O ACESSO NÃO É UM VISITANTE NOVO. Esta ação existe para a
  // pessoa cujo provisionamento FALHOU no primeiro acesso — não para quem teve
  // o vínculo retirado por um administrador. Sem esta guarda, revogar alguém
  // lhe dava, na prática, um tenant próprio dentro da mesma instalação.
  //
  // Medido em 2026-09-10: um membro revogado chegou até este formulário e só
  // foi barrado porque ainda tinha `invite_token` no `user_metadata` — acidente,
  // não guarda —, recebendo de volta "convite pendente ou inválido", que não
  // era a verdade sobre o que tinha acontecido com ele.
  if (await acessoFoiRevogado(authUser.id)) {
    void audit({
      action: "auth.signup_provision_recusado",
      actorUserId: authUser.id,
      metadata: { motivo: "acesso_revogado" },
    });
    return { ok: false, error: "access_revoked" };
  }

  const decisao = decidirConviteDoSignup(authUser);
  if (decisao.tipo !== "provisionar") {
    void audit({
      action: "auth.signup_provision_recusado",
      actorUserId: authUser.id,
      metadata: { motivo: decisao.tipo === "convite" ? "convite_pendente" : decisao.motivo },
    });
    return { ok: false, error: "invite_pending" };
  }

  // A QUARTA PORTA, e ela não estava no desenho original — apareceu medindo.
  // Esta action também provisiona organização, então numa instalação em
  // `so_convite` ela seria a saída de emergência que reabre o que as outras
  // três fecharam: bastaria criar a conta por qualquer via, chegar sem vínculo,
  // e pedir a recuperação. Fechar só a tela de cadastro seria outro capacho.
  const modo = await modoDeCadastro();
  if (modo === "so_convite") {
    void audit({
      action: "auth.signup_provision_recusado",
      actorUserId: authUser.id,
      metadata: { motivo: "somente_convite" },
    });
    return { ok: false, error: "somente_convite" };
  }

  if (await authRateLimited("org_recovery", authUser.id, AUTH_LIMITS.org_recovery)) {
    return { ok: false, error: "rate_limited" };
  }

  // COM APROVAÇÃO (migration 0383, recorte do PR #714): esta é a ÚNICA porta
  // que cria o pedido — `/auth/confirm` e `/auth/callback` mandam para cá em
  // vez de provisionar. A empresa nasce só na aprovação do administrador da
  // instalação (`app/actions/registration/decide.ts`).
  if (modo === "com_aprovacao") {
    // Recusa é final. Sem esta guarda, recusar seria decoração: bastaria
    // enviar o pedido de novo, e o índice só impede DOIS pendentes.
    if ((await estadoDoPedido(authUser.id)) === "rejected") {
      return { ok: false, error: "pedido_recusado" };
    }
    try {
      // A empresa só nasce na APROVAÇÃO, que lê o plano da conta
      // (`decide.ts`): a escolha fica guardada nela, pela sessão da própria pessoa.
      if (plano.data) {
        const { error: erroDoPlano } = await supabase.auth.updateUser({ data: comPlano });
        invalidarUsuario(authUser.id); // #3696: o metadata da sessão vai para o cache curto
        if (erroDoPlano) throw new Error(`plano do pedido não gravado: ${erroDoPlano.message}`);
      }
      const pedido = await createRegistrationRequest(authUser.id, parsed.data);
      if (pedido.created) {
        void audit({
          action: "registration.requested",
          actorUserId: authUser.id,
          resourceType: "registration_request",
          resourceId: pedido.id,
        });
      }
    } catch (error) {
      void audit({
        action: "auth.signup_provision_recovery_failed",
        actorUserId: authUser.id,
        metadata: { reason: error instanceof Error ? error.message : String(error) },
      });
      return { ok: false, error: "provision_failed" };
    }
    revalidatePath("/get-started");
    redirect("/get-started");
  }

  try {
    const provisioned = await ensureTenantForUser(
      {
        id: authUser.id,
        email: authUser.email,
        user_metadata: { ...authUser.user_metadata, org_name: parsed.data, ...comPlano },
      },
      { source: "recovery" },
    );
    if (!provisioned.organizationId) return { ok: false, error: "provision_failed" };
  } catch (error) {
    const reason = error instanceof Error ? error.message : String(error);
    void audit({
      action: "auth.signup_provision_recovery_failed",
      actorUserId: authUser.id,
      metadata: { reason },
    });
    // A recusa do gatilho (0679) tem saída nesta mesma tela: escolher um plano.
    return { ok: false, error: reason.includes("plano_obrigatorio") ? "plano_obrigatorio" : "provision_failed" };
  }

  revalidatePath("/app");
  redirect("/onboarding/welcome");
}
