import { and, eq } from "drizzle-orm";
import { headers } from "next/headers";

import { redirect } from "@/i18n/navigation";
import { routing, type Locale } from "@/i18n/routing";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
import { member } from "@/lib/db/schema";

type Session = NonNullable<Awaited<ReturnType<typeof auth.api.getSession>>>;

/* Returns the session if the user is signed in AND has role === "admin".
   Redirects to /login if unauthenticated, to /dashboard if authenticated
   but not admin. */
export async function requireAdmin(locale: Locale = routing.defaultLocale) {
  const session = await auth.api.getSession({ headers: await headers() });
  if (!session) redirect({ href: "/login", locale });

  // Better Auth admin plugin extends user with `role`.
  const role = (session!.user as { role?: string | null }).role;
  if (role !== "admin") redirect({ href: "/dashboard", locale });

  return session!;
}

/* Resolves the active org for a session.
   1. Reads session.activeOrganizationId (the canonical source) — fast path.
   2. Falls back to the user's earliest membership if NULL.
   3. When the fallback fires, persists the choice via setActiveOrganization
      so subsequent requests skip the fallback path.

   Returns the org id, or null if the user has no memberships at all
   (which shouldn't happen post-Phase 1 but we don't assume). */
export async function resolveActiveOrgId(session: Session): Promise<string | null> {
  const fromSession = (session.session as { activeOrganizationId?: string | null })
    .activeOrganizationId;
  if (fromSession) return fromSession;

  // Fallback: pick the user's earliest membership.
  const rows = await db
    .select({ organizationId: member.organizationId })
    .from(member)
    .where(eq(member.userId, session.user.id))
    .orderBy(member.createdAt)
    .limit(1);
  if (rows.length === 0) return null;
  const orgId = rows[0]!.organizationId;

  // Persist for next time. Don't await any error — if this fails the
  // request still works thanks to the fallback path.
  try {
    await auth.api.setActiveOrganization({
      headers: await headers(),
      body: { organizationId: orgId },
    });
  } catch {
    // Silent: this is a best-effort cache write. Subsequent calls will
    // still hit the fallback if it persistently fails.
  }
  return orgId;
}

/* Returns { session, activeOrganizationId } for a signed-in user with an
   active org. Redirects to /login if unauthenticated. If the user has no
   active org AND no memberships, redirects to /onboarding (Phase 3). */
export async function requireOrgSession(locale: Locale = routing.defaultLocale) {
  const session = await auth.api.getSession({ headers: await headers() });
  if (!session) redirect({ href: "/login", locale });

  const activeOrganizationId = await resolveActiveOrgId(session!);
  if (!activeOrganizationId) redirect({ href: "/onboarding", locale });

  return { session: session!, activeOrganizationId: activeOrganizationId! };
}

/* Reads `session.session.activeOrganizationId` without redirecting.
   Returns null if no session or no active org found (after fallback).
   Use this in API routes (which return JSON, not redirects). Server
   Components should prefer requireOrgSession() above. */
export async function getActiveOrgIdFromSession(
  session: Session | null | undefined,
): Promise<string | null> {
  if (!session) return null;
  return resolveActiveOrgId(session);
}

export type OrgRole = "owner" | "admin" | "member";

const ROLE_RANK: Record<OrgRole, number> = { member: 0, admin: 1, owner: 2 };

/* Resolves the user's active org AND their role in it. Redirects /login
   if unauth, /onboarding if no active org. Throws Error("Forbidden: ...")
   if the user is signed in + has an active org but is NOT a member of
   it (defensive; shouldn't happen post Phase 1), or if their role is
   below minRole.

   Use this in Server Components and Server Actions that need a role
   check. For pure session+org resolution (no role check), use
   requireOrgSession instead. */
export async function requireOrgMember(opts: { minRole?: OrgRole; locale?: Locale } = {}) {
  const locale = opts.locale ?? routing.defaultLocale;
  const { session, activeOrganizationId } = await requireOrgSession(locale);

  const rows = await db
    .select({ role: member.role })
    .from(member)
    .where(and(eq(member.userId, session.user.id), eq(member.organizationId, activeOrganizationId)))
    .limit(1);

  if (rows.length === 0) {
    throw new Error("Forbidden: not a member of the active organization");
  }
  const role = rows[0]!.role as OrgRole;

  if (opts.minRole && ROLE_RANK[role] < ROLE_RANK[opts.minRole]) {
    throw new Error(`Forbidden: requires role ${opts.minRole}, has ${role}`);
  }

  return { session, user: session.user, activeOrganizationId, role };
}
