import { useCallback, useEffect, useState } from "react";
import {
  ActionIcon,
  Alert,
  Badge,
  Button,
  Card,
  CopyButton,
  Group,
  Loader,
  Modal,
  NumberInput,
  Select,
  Stack,
  Table,
  Text,
  TextInput,
  Title,
  Tooltip,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { notifications } from "@mantine/notifications";
import {
  IconAlertTriangle,
  IconCheck,
  IconCopy,
  IconPlus,
  IconRefresh,
  IconTicket,
  IconTrash,
  IconUserOff,
} from "@tabler/icons-react";
import { api, ApiError } from "../api";
import type { InviteView, UserView } from "../types";
import { fmtTime, trunc } from "../util";

function notifyErr(e: unknown) {
  notifications.show({ color: "red", title: "Error", message: e instanceof ApiError ? e.message : String(e) });
}

// resolveJoinURL prefers the gateway-built link; when the gateway has no client
// base URL configured it falls back to the panel's own origin so the link is at
// least clickable, and the caller surfaces a warning to configure it properly.
function resolveJoinURL(inv: { join_url: string; token: string }): string {
  if (inv.join_url) return inv.join_url;
  return `${window.location.origin}/join?token=${inv.token}`;
}

export function UsersPage({ usersBackend, joinBaseURL }: { usersBackend: string; joinBaseURL: string }) {
  const [users, setUsers] = useState<UserView[] | null>(null);
  const [invites, setInvites] = useState<InviteView[]>([]);
  const [err, setErr] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const [addOpen, addCtl] = useDisclosure(false);
  const [createOpen, createCtl] = useDisclosure(false);
  const [toDelete, setToDelete] = useState<UserView | null>(null);

  const load = useCallback(() => {
    setLoading(true);
    Promise.all([api.listUsers(), api.listInvites().catch(() => [] as InviteView[])])
      .then(([u, inv]) => {
        setUsers(u);
        setInvites(inv);
        setErr(null);
      })
      .catch((e: ApiError) => setErr(e.message))
      .finally(() => setLoading(false));
  }, []);

  useEffect(() => { load(); }, [load]);

  const revoke = async (u: UserView) => {
    if (!window.confirm(`¿Revocar a "${u.handle}"? Pierde acceso al bus en AMBOS planos de inmediato (control y datos). La identidad permanece en la lista como revocada (auditable).`)) return;
    try {
      await api.revokeUser(u.sign_pub);
      notifications.show({ color: "teal", title: "Revocado", message: u.handle });
      load();
    } catch (e) {
      notifyErr(e);
    }
  };

  return (
    <Stack gap="md">
      <Group justify="space-between">
        <Group gap="sm">
          <Title order={3}>Users</Title>
          {users && <Badge color="brand" variant="light">{users.length}</Badge>}
          <Tooltip label="Vía de gestión de la allowlist del bus">
            <Badge variant="outline" color="teal" style={{ textTransform: "none" }}>
              backend: {usersBackend}
            </Badge>
          </Tooltip>
        </Group>
        <Group gap="xs">
          <Tooltip label="Refrescar">
            <ActionIcon variant="light" color="brand" onClick={load} loading={loading}>
              <IconRefresh size={18} />
            </ActionIcon>
          </Tooltip>
          <Button variant="default" leftSection={<IconPlus size={16} />} onClick={addCtl.open}>
            Añadir user (clave conocida)
          </Button>
          <Button leftSection={<IconTicket size={16} />} onClick={createCtl.open}>
            Crear usuario
          </Button>
        </Group>
      </Group>

      {err && <Text c="red">{err}</Text>}
      {!users && !err && <Loader color="brand" />}

      {invites.length > 0 && (
        <Card withBorder bg="dark.7" radius="md">
          <Stack gap="sm">
            <Group gap="sm">
              <IconTicket size={18} />
              <Text fw={600}>Invitaciones pendientes</Text>
              <Badge color="brand" variant="light">{invites.length}</Badge>
            </Group>
            <Table verticalSpacing="xs" horizontalSpacing="md">
              <Table.Thead>
                <Table.Tr>
                  <Table.Th>Handle</Table.Th>
                  <Table.Th>Rol</Table.Th>
                  <Table.Th>Token</Table.Th>
                  <Table.Th>Caduca</Table.Th>
                  <Table.Th>Enlace</Table.Th>
                </Table.Tr>
              </Table.Thead>
              <Table.Tbody>
                {invites.map((inv) => (
                  <Table.Tr key={inv.token}>
                    <Table.Td><Text fw={600}>{inv.handle}</Text></Table.Td>
                    <Table.Td><Badge variant="dot" color={inv.role === "admin" ? "brand" : "gray"}>{inv.role}</Badge></Table.Td>
                    <Table.Td><Text size="xs" c="dimmed" style={{ fontFamily: "monospace" }}>{trunc(inv.token, 10, 6)}</Text></Table.Td>
                    <Table.Td><Text size="xs" c="dimmed">{fmtTime(inv.expires_at)}</Text></Table.Td>
                    <Table.Td>
                      <CopyButton value={resolveJoinURL(inv)}>
                        {({ copied, copy }) => (
                          <Button size="xs" variant="light" color={copied ? "teal" : "brand"} leftSection={copied ? <IconCheck size={14} /> : <IconCopy size={14} />} onClick={copy}>
                            {copied ? "Copiado" : "Copiar enlace"}
                          </Button>
                        )}
                      </CopyButton>
                    </Table.Td>
                  </Table.Tr>
                ))}
              </Table.Tbody>
            </Table>
          </Stack>
        </Card>
      )}

      {users && (
        <Card withBorder bg="dark.7" p={0} radius="md">
          <Table verticalSpacing="sm" horizontalSpacing="md" highlightOnHover>
            <Table.Thead>
              <Table.Tr>
                <Table.Th>Handle</Table.Th>
                <Table.Th>Rol</Table.Th>
                <Table.Th>Estado</Table.Th>
                <Table.Th>sign_pub</Table.Th>
                <Table.Th>Creado</Table.Th>
                <Table.Th />
              </Table.Tr>
            </Table.Thead>
            <Table.Tbody>
              {users.map((u) => (
                <Table.Tr key={u.sign_pub}>
                  <Table.Td><Text fw={600}>{u.handle}</Text></Table.Td>
                  <Table.Td><Badge variant="dot" color={u.role === "admin" ? "brand" : "gray"}>{u.role}</Badge></Table.Td>
                  <Table.Td>
                    <Badge variant="light" color={u.status === "active" ? "teal" : "red"}>{u.status}</Badge>
                  </Table.Td>
                  <Table.Td>
                    <Tooltip label={u.sign_pub}>
                      <Text size="xs" c="dimmed" style={{ fontFamily: "monospace" }}>{trunc(u.sign_pub, 12, 8)}</Text>
                    </Tooltip>
                  </Table.Td>
                  <Table.Td><Text size="xs" c="dimmed">{fmtTime(u.created_at)}</Text></Table.Td>
                  <Table.Td>
                    <Group gap={4} justify="flex-end" wrap="nowrap">
                      {u.status === "active" && (
                        <Tooltip label="Revocar acceso (deja rastro auditable)">
                          <ActionIcon variant="subtle" color="orange" onClick={() => revoke(u)}>
                            <IconUserOff size={16} />
                          </ActionIcon>
                        </Tooltip>
                      )}
                      <Tooltip label="Eliminar (borrado permanente)">
                        <ActionIcon variant="subtle" color="red" onClick={() => setToDelete(u)}>
                          <IconTrash size={16} />
                        </ActionIcon>
                      </Tooltip>
                    </Group>
                  </Table.Td>
                </Table.Tr>
              ))}
            </Table.Tbody>
          </Table>
        </Card>
      )}

      <AddUserModal opened={addOpen} onClose={addCtl.close} onAdded={load} />
      <CreateInviteModal opened={createOpen} onClose={createCtl.close} onCreated={load} joinBaseURL={joinBaseURL} />
      <DeleteUserModal user={toDelete} onClose={() => setToDelete(null)} onDeleted={load} />
    </Stack>
  );
}

// AddUserModal registers a user from a sign_pub the admin already holds (the
// pre-invite flow, kept for advanced use). The wallet-model path is CreateInvite.
function AddUserModal({ opened, onClose, onAdded }: { opened: boolean; onClose: () => void; onAdded: () => void }) {
  const [handle, setHandle] = useState("");
  const [signPub, setSignPub] = useState("");
  const [role, setRole] = useState<string>("member");
  const [busy, setBusy] = useState(false);

  const submit = async () => {
    setBusy(true);
    try {
      await api.addUser({ handle: handle.trim(), sign_pub: signPub.trim(), role });
      notifications.show({ color: "teal", title: "User añadido", message: handle });
      setHandle(""); setSignPub(""); setRole("member");
      onClose();
      onAdded();
    } catch (e) {
      notifyErr(e);
    } finally {
      setBusy(false);
    }
  };

  const ready = handle.trim().length > 0 && signPub.trim().length === 64;

  return (
    <Modal opened={opened} onClose={onClose} title="Añadir user (clave ya conocida)" centered>
      <Stack gap="sm">
        <Text size="xs" c="dimmed">
          Para cuando ya tienes la clave pública del usuario. Para el alta normal sin manejar
          claves, usa «Crear usuario» (enlace de invitación).
        </Text>
        <TextInput label="Handle" placeholder="ana" value={handle} onChange={(e) => setHandle(e.currentTarget.value)} data-autofocus />
        <TextInput
          label="sign_pub (hex, 64)"
          description="Clave pública Ed25519 del usuario (la misma que autentica control + datos)"
          placeholder="48bc0dc8…"
          value={signPub}
          onChange={(e) => setSignPub(e.currentTarget.value)}
          error={signPub.length > 0 && signPub.trim().length !== 64 ? "64 chars hex" : undefined}
        />
        <Select label="Rol" data={[{ value: "member", label: "member" }, { value: "admin", label: "admin" }]} value={role} onChange={(v) => setRole(v || "member")} allowDeselect={false} />
        <Group justify="flex-end">
          <Button variant="default" onClick={onClose}>Cancelar</Button>
          <Button onClick={submit} loading={busy} disabled={!ready}>Añadir</Button>
        </Group>
      </Stack>
    </Modal>
  );
}

// CreateInviteModal is the wallet-model account creation: the admin fixes a handle
// and role and gets back a single-use join link. The admin never sees a private
// key — the user generates its own keypair when it opens the link.
function CreateInviteModal({
  opened,
  onClose,
  onCreated,
  joinBaseURL,
}: {
  opened: boolean;
  onClose: () => void;
  onCreated: () => void;
  joinBaseURL: string;
}) {
  const [handle, setHandle] = useState("");
  const [role, setRole] = useState<string>("member");
  const [days, setDays] = useState<number | string>(7);
  const [busy, setBusy] = useState(false);
  const [created, setCreated] = useState<InviteView | null>(null);

  const reset = () => { setHandle(""); setRole("member"); setDays(7); setCreated(null); };
  const close = () => { reset(); onClose(); };

  const submit = async () => {
    setBusy(true);
    try {
      const ttl = typeof days === "number" ? days * 86400 : 0;
      const inv = await api.createInvite({ handle: handle.trim(), role, ttl_secs: ttl });
      setCreated(inv);
      onCreated();
    } catch (e) {
      notifyErr(e);
    } finally {
      setBusy(false);
    }
  };

  const ready = handle.trim().length > 0;
  const joinURL = created ? resolveJoinURL(created) : "";

  return (
    <Modal opened={opened} onClose={close} title="Crear usuario (enlace de invitación)" centered>
      {!created ? (
        <Stack gap="sm">
          <Text size="xs" c="dimmed">
            Genera un enlace de un solo uso. El usuario lo abre en su dispositivo, crea ahí su
            clave (la privada nunca sale de su equipo) y se registra. Tú no manejas ninguna clave.
          </Text>
          <TextInput label="Handle" placeholder="ana" value={handle} onChange={(e) => setHandle(e.currentTarget.value)} data-autofocus />
          <Select label="Rol" data={[{ value: "member", label: "member" }, { value: "admin", label: "admin" }]} value={role} onChange={(v) => setRole(v || "member")} allowDeselect={false} />
          <NumberInput label="Caducidad (días)" min={1} max={90} value={days} onChange={setDays} />
          <Group justify="flex-end">
            <Button variant="default" onClick={close}>Cancelar</Button>
            <Button onClick={submit} loading={busy} disabled={!ready} leftSection={<IconTicket size={16} />}>Crear enlace</Button>
          </Group>
        </Stack>
      ) : (
        <Stack gap="sm">
          <Alert color="teal" icon={<IconCheck size={16} />} title={`Invitación para "${created.handle}" (${created.role})`}>
            Comparte este enlace con el usuario. Es de un solo uso y caduca el {fmtTime(created.expires_at)}.
          </Alert>
          {!joinBaseURL && (
            <Alert color="yellow" icon={<IconAlertTriangle size={16} />} title="Base URL del cliente sin configurar">
              El enlace usa el origen de este panel como respaldo. Configura el cliente real con
              <Text span fw={600}> --join-base-url </Text> o la variable
              <Text span fw={600}> UNIBUS_JOIN_BASE_URL </Text> en el gateway.
            </Alert>
          )}
          <TextInput readOnly value={joinURL} styles={{ input: { fontFamily: "monospace", fontSize: 12 } }} />
          <Group justify="space-between">
            <CopyButton value={joinURL}>
              {({ copied, copy }) => (
                <Button color={copied ? "teal" : "brand"} leftSection={copied ? <IconCheck size={16} /> : <IconCopy size={16} />} onClick={copy}>
                  {copied ? "Copiado" : "Copiar enlace"}
                </Button>
              )}
            </CopyButton>
            <Button variant="default" onClick={close}>Cerrar</Button>
          </Group>
        </Stack>
      )}
    </Modal>
  );
}

// DeleteUserModal is the STRONG confirmation for a permanent hard-delete. Unlike
// revoke (a one-click window.confirm), purging requires typing the handle, so it
// cannot be triggered by a stray click. The wording makes the irreversibility and
// the difference from revoke explicit.
function DeleteUserModal({ user, onClose, onDeleted }: { user: UserView | null; onClose: () => void; onDeleted: () => void }) {
  const [confirm, setConfirm] = useState("");
  const [busy, setBusy] = useState(false);

  useEffect(() => { setConfirm(""); }, [user]);

  if (!user) return null;

  const submit = async () => {
    setBusy(true);
    try {
      await api.deleteUser(user.sign_pub);
      notifications.show({ color: "red", title: "Usuario eliminado", message: `${user.handle} purgado del allowlist` });
      onClose();
      onDeleted();
    } catch (e) {
      notifyErr(e);
    } finally {
      setBusy(false);
    }
  };

  const ready = confirm.trim() === user.handle;

  return (
    <Modal opened={!!user} onClose={onClose} title="Borrado permanente" centered>
      <Stack gap="sm">
        <Alert color="red" icon={<IconAlertTriangle size={16} />} title="Esto NO es revocar">
          <Text size="sm">
            Vas a <Text span fw={700}>BORRAR PERMANENTEMENTE</Text> a «{user.handle}» del allowlist
            del bus. Se elimina por completo (sin rastro auditable, a diferencia de revocar). El
            usuario deja de poder autenticarse en ambos planos; sus membresías de rooms quedan
            inertes. Esta acción es irreversible.
          </Text>
        </Alert>
        <TextInput
          label={`Escribe «${user.handle}» para confirmar`}
          placeholder={user.handle}
          value={confirm}
          onChange={(e) => setConfirm(e.currentTarget.value)}
          data-autofocus
        />
        <Group justify="flex-end">
          <Button variant="default" onClick={onClose}>Cancelar</Button>
          <Button color="red" leftSection={<IconTrash size={16} />} onClick={submit} loading={busy} disabled={!ready}>
            Eliminar permanentemente
          </Button>
        </Group>
      </Stack>
    </Modal>
  );
}
