"use client";

import Script from "next/script";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useRef, useState } from "react";

import { api, ApiError } from "@/lib/api/browser";

/**
 * Admin sign-in: Google, a username and password, and — in a dev build only — an email.
 *
 * The browser never sees an application credential on the Google path. Google returns an ID
 * token, this posts it to FastAPI, and FastAPI verifies the signature, checks the account and
 * issues its own httpOnly session cookie. Nothing about the session is readable from
 * JavaScript, which is still true now that the cookie holds a JWT (design D12): "JWT-based"
 * describes the token's format, not where it is kept.
 *
 * Three outcomes are possible beyond success, and they are not the same:
 *
 * * **Awaiting approval** — a first Google sign-in created a pending account. Shown plainly,
 *   because self-registration already reveals that the address is not on the roster, and a
 *   generic refusal would strand a legitimate host at what reads as a bug (design D7).
 * * **Access withdrawn** — the account exists and somebody turned it off.
 * * **Not authorized** — everything else, deliberately indistinguishable. Every password
 *   failure lands here: an unknown username and a wrong password must answer the same.
 *
 * `clientId` is null until a real OAuth client exists. Rather than render a button that
 * throws, the component says so plainly — a broken sign-in with no explanation is the worst
 * possible first screen.
 */
declare global {
  interface Window {
    google?: {
      accounts: {
        id: {
          initialize(config: {
            client_id: string;
            callback: (response: { credential: string }) => void;
            auto_select?: boolean;
            cancel_on_tap_outside?: boolean;
          }): void;
          renderButton(parent: HTMLElement, options: Record<string, unknown>): void;
        };
      };
    };
  }
}

type Outcome =
  | { kind: "none" }
  | { kind: "error"; message: string }
  | { kind: "pending"; message: string }
  | { kind: "withdrawn"; message: string };

/**
 * A 403 from either sign-in path is an account-state answer; a 401 is a credential answer.
 * Branching on the status rather than the wording, so rephrasing a message never changes
 * which panel appears.
 */
function outcomeOf(err: unknown): Outcome {
  if (!(err instanceof ApiError)) {
    return { kind: "error", message: "Sign-in failed. Please try again." };
  }
  if (err.status === 403) {
    const withdrawn = err.message.toLowerCase().includes("withdrawn");
    return { kind: withdrawn ? "withdrawn" : "pending", message: err.message };
  }
  return { kind: "error", message: err.message };
}

export default function SignInForm({
  clientId,
  devBypassEnabled,
}: {
  clientId: string | null;
  devBypassEnabled: boolean;
}) {
  const router = useRouter();
  const buttonRef = useRef<HTMLDivElement>(null);
  const [outcome, setOutcome] = useState<Outcome>({ kind: "none" });
  const [busy, setBusy] = useState(false);
  const [devEmail, setDevEmail] = useState("");
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");

  const complete = useCallback(() => {
    // The protected layout decides where to land — a session holding a temporary password
    // is sent to the change-password screen rather than the dashboard.
    router.replace("/admin");
    router.refresh();
  }, [router]);

  const attempt = useCallback(
    async (run: () => Promise<unknown>) => {
      setBusy(true);
      setOutcome({ kind: "none" });
      try {
        await run();
        complete();
      } catch (err) {
        setOutcome(outcomeOf(err));
        setBusy(false);
      }
    },
    [complete],
  );

  const handleCredential = useCallback(
    (idToken: string) => attempt(() => api.signInWithGoogle(idToken)),
    [attempt],
  );

  const [scriptReady, setScriptReady] = useState(false);

  useEffect(() => {
    if (!clientId || !scriptReady || !buttonRef.current || !window.google) return;
    window.google.accounts.id.initialize({
      client_id: clientId,
      callback: (response) => void handleCredential(response.credential),
      auto_select: false,
      cancel_on_tap_outside: true,
    });
    window.google.accounts.id.renderButton(buttonRef.current, {
      theme: "outline",
      size: "large",
      width: 288,
      text: "signin_with",
    });
  }, [clientId, scriptReady, handleCredential]);

  return (
    <div className="mt-6 space-y-5">
      {clientId ? (
        <>
          <Script
            src="https://accounts.google.com/gsi/client"
            strategy="afterInteractive"
            onReady={() => setScriptReady(true)}
          />
          <div ref={buttonRef} className="flex justify-center" />
        </>
      ) : (
        <p className="rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900">
          Google sign-in is not configured yet. Add a real OAuth client id to
          <code className="mx-1 rounded bg-amber-100 px-1">GOOGLE_CLIENT_ID</code>
          with this origin registered as an authorized JavaScript origin.
        </p>
      )}

      <div className="flex items-center gap-3" aria-hidden="true">
        <span className="h-px flex-1 bg-stone-200" />
        <span className="text-xs uppercase tracking-wide text-stone-400">or</span>
        <span className="h-px flex-1 bg-stone-200" />
      </div>

      <form
        onSubmit={(e) => {
          e.preventDefault();
          void attempt(() => api.signInWithPassword(username, password));
        }}
        className="space-y-3"
      >
        <div>
          <label htmlFor="username" className="block text-xs font-medium text-stone-500">
            Username
          </label>
          <input
            id="username"
            name="username"
            autoComplete="username"
            required
            value={username}
            onChange={(e) => setUsername(e.target.value)}
            className="mt-1 w-full rounded-md border border-stone-300 px-3 py-2 text-sm focus:border-stone-500 focus:outline-none focus:ring-2 focus:ring-stone-200"
          />
        </div>
        <div>
          <label htmlFor="password" className="block text-xs font-medium text-stone-500">
            Password
          </label>
          <input
            id="password"
            name="password"
            type="password"
            autoComplete="current-password"
            required
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            className="mt-1 w-full rounded-md border border-stone-300 px-3 py-2 text-sm focus:border-stone-500 focus:outline-none focus:ring-2 focus:ring-stone-200"
          />
        </div>
        <button
          type="submit"
          disabled={busy}
          className="w-full rounded-md bg-stone-900 px-4 py-2 text-sm font-medium text-white hover:bg-stone-800 disabled:opacity-50"
        >
          {busy ? "Signing in…" : "Sign in"}
        </button>
        <p className="text-xs text-stone-500">
          Forgotten your password? Ask a super admin to issue you a new temporary one — there
          is no reset link.
        </p>
      </form>

      {devBypassEnabled && (
        // Visually separated and labelled, because a real password form now sits directly
        // above it and the two must not be mistaken for one another (design D9).
        <form
          onSubmit={(e) => {
            e.preventDefault();
            void attempt(() => api.signInAsDev(devEmail));
          }}
          className="space-y-2 rounded-lg border border-dashed border-stone-300 bg-stone-50 p-3"
        >
          <label htmlFor="dev-email" className="block text-xs font-medium text-stone-500">
            Development shortcut — no password. Refused unless the API is a dev build with the
            bypass enabled.
          </label>
          <div className="flex gap-2">
            <input
              id="dev-email"
              type="email"
              required
              value={devEmail}
              onChange={(e) => setDevEmail(e.target.value)}
              placeholder="admin@example.com"
              className="min-w-0 flex-1 rounded-md border border-stone-300 px-3 py-2 text-sm focus:border-stone-500 focus:outline-none focus:ring-2 focus:ring-stone-200"
            />
            <button
              type="submit"
              disabled={busy}
              className="rounded-md border border-stone-400 px-4 py-2 text-sm font-medium text-stone-700 hover:bg-stone-100 disabled:opacity-50"
            >
              Continue
            </button>
          </div>
        </form>
      )}

      {outcome.kind === "pending" && (
        <div
          role="status"
          className="rounded-lg border border-sky-200 bg-sky-50 px-3 py-2 text-sm text-sky-900"
        >
          <p className="font-medium">Awaiting approval</p>
          <p className="mt-1">{outcome.message}</p>
          <p className="mt-1 text-sky-800">
            Your account has been created and is waiting for a super admin. Try again once
            they have approved you.
          </p>
        </div>
      )}

      {outcome.kind === "withdrawn" && (
        <p
          role="alert"
          className="rounded-lg border border-stone-300 bg-stone-100 px-3 py-2 text-sm text-stone-700"
        >
          {outcome.message} Contact a super admin if you think this is a mistake.
        </p>
      )}

      {outcome.kind === "error" && (
        <p role="alert" className="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700">
          {outcome.message}
        </p>
      )}
    </div>
  );
}
