import { useEffect, useMemo, useState } from "react";
import {
  Alert,
  Button,
  Card,
  Center,
  Checkbox,
  CopyButton,
  Group,
  Loader,
  PasswordInput,
  SimpleGrid,
  Stack,
  Text,
  TextInput,
} from "@mantine/core";
import {
  IconAlertTriangle,
  IconCheck,
  IconCopy,
  IconKey,
  IconShieldLock,
} from "@tabler/icons-react";
import { SessionError } from "./busService";
import { AuthCard, AuthHeader } from "./AuthShell";
import type { User } from "./types";
import { newMnemonic, mnemonicWords } from "./wallet/bip39";
import { deriveIdentity, type WalletIdentity } from "./wallet/derive";
import { saveAndOpen } from "./wallet/account";

type Step = "generating" | "show-seed" | "confirm-seed" | "password" | "joining";

// pickPositions chooses `count` distinct word positions (0-based) to ask the user
// to confirm. This is a UI choice, not key material, so Math.random is fine.
function pickPositions(total: number, count: number): number[] {
  const all = Array.from({ length: total }, (_, i) => i);
  for (let i = all.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [all[i], all[j]] = [all[j], all[i]];
  }
  return all.slice(0, count).sort((a, b) => a - b);
}

// Join is the onboarding page reached from an invite link (/join?token=XXX). It
// generates a brand-new BIP39 seed, derives the identity, shows the seed exactly
// once with a confirmation gate, takes a local password, registers the PUBLIC key
// with the bus using the token, and enters the chat. The seed is never persisted
// and never sent to the server.
export function Join({
  token,
  onJoined,
  onRecover,
}: {
  token: string;
  onJoined: (u: User) => void;
  onRecover: () => void;
}) {
  const [step, setStep] = useState<Step>("generating");
  const [mnemonic, setMnemonic] = useState("");
  const [identity, setIdentity] = useState<WalletIdentity | null>(null);
  const [error, setError] = useState<string | null>(null);

  // Generate the seed + identity once on mount. Deriving is fast and pure.
  useEffect(() => {
    if (!token) {
      setError("Enlace de invitación inválido: falta el token.");
      return;
    }
    try {
      const m = newMnemonic();
      setMnemonic(m);
      setIdentity(deriveIdentity(m));
      setStep("show-seed");
    } catch {
      setError("No se pudo generar la identidad en este navegador.");
    }
  }, [token]);

  const words = useMemo(() => mnemonicWords(mnemonic), [mnemonic]);

  if (error && step === "generating") {
    return (
      <AuthCard>
        <Alert color="red" icon={<IconAlertTriangle size={18} />} title="Error">
          {error}
        </Alert>
        <Button variant="light" mt="md" onClick={onRecover}>
          Recuperar con mi seed
        </Button>
      </AuthCard>
    );
  }

  if (step === "generating" || !identity) {
    return (
      <Center h="100vh" bg="dark.9">
        <Loader color="brand" />
      </Center>
    );
  }

  if (step === "show-seed") {
    return (
      <ShowSeed words={words} onContinue={() => setStep("confirm-seed")} />
    );
  }

  if (step === "confirm-seed") {
    return (
      <ConfirmSeed
        words={words}
        onBack={() => setStep("show-seed")}
        onConfirmed={() => setStep("password")}
      />
    );
  }

  // step === "password" | "joining"
  return (
    <SetPassword
      busy={step === "joining"}
      error={error}
      onSubmit={async (password) => {
        setStep("joining");
        setError(null);
        try {
          // The bus has no token-register endpoint (that was a gateway mock): a
          // browser cannot self-register on an enforce cluster. The identity must be
          // allow-listed by an admin first. We persist it locally and try to open the
          // session; if the identity is not yet authorized, openSession fails and we
          // tell the user to have an admin authorize their public key.
          const handle = identity.signPub.slice(0, 8);
          const user = await saveAndOpen(identity, handle, password);
          onJoined(user);
        } catch (e) {
          const base =
            e instanceof SessionError || e instanceof Error
              ? e.message
              : "No se pudo completar el alta.";
          setError(
            `${base}. Pide a un administrador que autorice tu clave pública: ${identity.signPub}`,
          );
          setStep("password");
        }
      }}
    />
  );
}

// ---- sub-screens ----------------------------------------------------------

function ShowSeed({
  words,
  onContinue,
}: {
  words: string[];
  onContinue: () => void;
}) {
  const [acknowledged, setAcknowledged] = useState(false);
  const phrase = words.join(" ");
  return (
    <AuthCard>
      <AuthHeader
        icon={<IconShieldLock size={30} />}
        title="Guarda tu frase de recuperación"
        subtitle="Estas 12 palabras son tu ÚNICA forma de recuperar tu cuenta si olvidas la contraseña o cambias de dispositivo. No las compartas con nadie."
      />
      <Card bg="dark.8" radius="md" p="md" withBorder>
        <SimpleGrid cols={3} spacing="xs" verticalSpacing="xs">
          {words.map((w, i) => (
            <Group gap={6} wrap="nowrap" key={i}>
              <Text size="xs" c="dimmed" w={18} ta="right">
                {i + 1}
              </Text>
              <Text size="sm" ff="monospace" fw={600}>
                {w}
              </Text>
            </Group>
          ))}
        </SimpleGrid>
      </Card>
      <Group justify="space-between">
        <CopyButton value={phrase}>
          {({ copied, copy }) => (
            <Button
              variant="subtle"
              size="xs"
              color={copied ? "teal" : "gray"}
              leftSection={
                copied ? <IconCheck size={14} /> : <IconCopy size={14} />
              }
              onClick={copy}
            >
              {copied ? "Copiada" : "Copiar"}
            </Button>
          )}
        </CopyButton>
      </Group>
      <Alert color="yellow" variant="light" icon={<IconAlertTriangle size={16} />}>
        unibus NO guarda esta frase. Si la pierdes y olvidas la contraseña, solo
        el administrador podrá darte de alta de nuevo.
      </Alert>
      <Checkbox
        checked={acknowledged}
        onChange={(e) => setAcknowledged(e.currentTarget.checked)}
        label="He guardado mi frase de recuperación en un lugar seguro"
      />
      <Button disabled={!acknowledged} onClick={onContinue}>
        Continuar
      </Button>
    </AuthCard>
  );
}

function ConfirmSeed({
  words,
  onBack,
  onConfirmed,
}: {
  words: string[];
  onBack: () => void;
  onConfirmed: () => void;
}) {
  // Ask the user to re-type 3 random words from their phrase. This proves they
  // actually wrote the seed down rather than clicking through.
  const positions = useMemo(() => pickPositions(words.length, 3), [words.length]);
  const [inputs, setInputs] = useState<Record<number, string>>({});
  const allCorrect = positions.every(
    (p) => (inputs[p] ?? "").trim().toLowerCase() === words[p],
  );
  const anyTyped = positions.some((p) => (inputs[p] ?? "").length > 0);
  return (
    <AuthCard>
      <AuthHeader
        icon={<IconCheck size={30} />}
        title="Confirma tu frase"
        subtitle="Escribe las palabras solicitadas para confirmar que la guardaste bien."
      />
      <Stack gap="sm">
        {positions.map((p) => (
          <TextInput
            key={p}
            label={`Palabra #${p + 1}`}
            placeholder={`palabra ${p + 1}`}
            value={inputs[p] ?? ""}
            error={
              (inputs[p] ?? "").length > 0 &&
              (inputs[p] ?? "").trim().toLowerCase() !== words[p]
                ? "No coincide"
                : undefined
            }
            onChange={(e) => {
              // Capture the value synchronously: React nulls e.currentTarget
              // after dispatch, so reading it inside the state updater (which runs
              // later) would throw "Cannot read properties of null".
              const v = e.currentTarget.value;
              setInputs((prev) => ({ ...prev, [p]: v }));
            }}
            autoComplete="off"
            spellCheck={false}
          />
        ))}
      </Stack>
      {!allCorrect && anyTyped && (
        <Text size="xs" c="dimmed">
          Revisa el orden y la ortografía de las palabras.
        </Text>
      )}
      <Group grow>
        <Button variant="default" onClick={onBack}>
          Volver a ver
        </Button>
        <Button disabled={!allCorrect} onClick={onConfirmed}>
          Confirmar
        </Button>
      </Group>
    </AuthCard>
  );
}

function SetPassword({
  busy,
  error,
  onSubmit,
}: {
  busy: boolean;
  error: string | null;
  onSubmit: (password: string) => void;
}) {
  const [pw, setPw] = useState("");
  const [pw2, setPw2] = useState("");
  const tooShort = pw.length > 0 && pw.length < 8;
  const mismatch = pw2.length > 0 && pw !== pw2;
  const ready = pw.length >= 8 && pw === pw2 && !busy;
  return (
    <AuthCard>
      <AuthHeader
        icon={<IconKey size={30} />}
        title="Protege tu identidad"
        subtitle="Elige una contraseña para cifrar tu clave en ESTE dispositivo. No se guarda ni se envía a ningún servidor; solo desbloquea tu clave local."
      />
      <PasswordInput
        label="Contraseña"
        description="Mínimo 8 caracteres"
        leftSection={<IconKey size={16} />}
        value={pw}
        error={tooShort ? "Demasiado corta" : undefined}
        onChange={(e) => setPw(e.currentTarget.value)}
        data-autofocus
      />
      <PasswordInput
        label="Repite la contraseña"
        leftSection={<IconKey size={16} />}
        value={pw2}
        error={mismatch ? "No coincide" : undefined}
        onChange={(e) => setPw2(e.currentTarget.value)}
        onKeyDown={(e) => e.key === "Enter" && ready && onSubmit(pw)}
      />
      {error && (
        <Text c="red" size="sm" ta="center">
          {error}
        </Text>
      )}
      <Button disabled={!ready} loading={busy} onClick={() => onSubmit(pw)}>
        Crear cuenta y entrar
      </Button>
    </AuthCard>
  );
}
