"use client";

/**
 * Account deletion — search, review, retire.
 *
 * Super admin only. Core enforces that on every endpoint; the tab and the loader
 * only keep the page out of everyone else's way.
 *
 * "Deletion" is a status move in Viewpoint, mirrored into the member database. No
 * row is removed, no booking is touched. Viewpoint is written first because it is
 * the system of record — the reverse order can leave an account Subito refuses to
 * serve while Viewpoint still shows it live, which is invisible from either console.
 *
 * The two systems' views of the account are shown side by side rather than merged.
 * Them disagreeing is the useful signal: a local status trailing Viewpoint means the
 * last sync did not land, and an operator should know that before acting.
 */

import {
  Alert,
  Badge,
  Button,
  Card,
  Container,
  Group,
  Paper,
  SimpleGrid,
  Skeleton,
  Stack,
  Table,
  Text,
  TextInput,
  Title,
} from "@mantine/core";
import { notifications } from "@mantine/notifications";
import {
  IconAlertTriangle,
  IconSearch,
  IconTrash,
  IconUserOff,
} from "@tabler/icons-react";
import { useCallback, useEffect, useState } from "react";
import {
  applyAccountDeletion,
  getDeletionConfig,
  listDeletionStatuses,
  searchAccounts,
  type AccountSearchResult,
  type DeletionConfig,
  type DeletionStatus,
} from "@/lib/features/account-deletion/query";
import { listEDMTemplates } from "@/lib/features/edm/query";
import { accountStatusTone } from "@/routes/members/_components/statusColors";
import styles from "@/routes/promo-code-campaigns/_components/promo.module.css";
import DeleteAccountModal, {
  type DeletionRequest,
  type TemplateOption,
} from "./_components/DeleteAccountModal";
import DeletionHistoryTable from "./_components/DeletionHistoryTable";
import RetiredAccountsTable from "./_components/RetiredAccountsTable";

/**
 * What the operator typed, read rather than declared.
 *
 * One box, no mode switch. An email and an account number are not confusable — an
 * address has an "@" and an account number does not — so asking which one is being
 * entered is a question the input already answers. The detection is shown back as a
 * hint so the operator can see it was read the way they meant.
 */
type SearchKind = "accountNumber" | "email";

const detectKind = (value: string): SearchKind =>
  value.includes("@") ? "email" : "accountNumber";

const formatDate = (value: string | null): string => {
  if (!value) return "—";
  const parsed = new Date(value);
  return Number.isFinite(parsed.getTime())
    ? parsed.toLocaleDateString()
    : value;
};

const AccountDeletionClientPage: React.FC = () => {
  const [term, setTerm] = useState("");
  const [searching, setSearching] = useState(false);
  const [searched, setSearched] = useState(false);
  const [results, setResults] = useState<AccountSearchResult[]>([]);

  const [statuses, setStatuses] = useState<DeletionStatus[]>([]);
  const [missingStatusIDs, setMissingStatusIDs] = useState<string[]>([]);
  const [statusesLoading, setStatusesLoading] = useState(true);

  const [templates, setTemplates] = useState<TemplateOption[]>([]);
  const [config, setConfig] = useState<DeletionConfig | null>(null);
  const [templatesLoading, setTemplatesLoading] = useState(true);

  const [target, setTarget] = useState<AccountSearchResult | null>(null);
  const [submitting, setSubmitting] = useState(false);
  // Bumped after a deletion so the history table pulls the new row in. A counter
  // rather than a callback ref: the table owns its own paging and search, and
  // reaching into it to prepend a row would fight both.
  const [historyToken, setHistoryToken] = useState(0);

  /*
   * The status list is fetched once on mount rather than when the modal opens.
   * It is small, it does not change between accounts, and loading it later would
   * leave the operator waiting inside a dialog they are about to confirm.
   */
  useEffect(() => {
    let cancelled = false;
    (async () => {
      const response = await listDeletionStatuses();
      if (cancelled) return;
      if (response.success && response.data) {
        setStatuses(response.data.items);
        setMissingStatusIDs(response.data.missingExtStatusIDs ?? []);
      } else {
        notifications.show({
          color: "red",
          message: response.message || "Failed to load account statuses",
        });
      }
      setStatusesLoading(false);
    })();
    return () => {
      cancelled = true;
    };
  }, []);

  /*
   * The EDM list and the configured defaults, also fetched once on mount.
   *
   * A failure here is not fatal and is not shouted about: the pickers fall back to
   * the default option alone, which is what the page did before templates were
   * selectable. Losing the ability to choose a template should not stop someone
   * retiring an account.
   */
  useEffect(() => {
    let cancelled = false;
    (async () => {
      const [templateResponse, configResponse] = await Promise.all([
        listEDMTemplates(),
        getDeletionConfig(),
      ]);
      if (cancelled) return;

      if (templateResponse.success && Array.isArray(templateResponse.data)) {
        setTemplates(
          templateResponse.data.map((template) => ({
            id: template.id,
            name: template.name,
          })),
        );
      }
      if (configResponse.success && configResponse.data) {
        setConfig(configResponse.data);
      }
      setTemplatesLoading(false);
    })();
    return () => {
      cancelled = true;
    };
  }, []);

  const runSearch = useCallback(async () => {
    const value = term.trim();
    if (!value) return;

    setSearching(true);
    const response = await searchAccounts(
      detectKind(value) === "email"
        ? { email: value }
        : { accountNumber: value },
    );
    setSearching(false);
    setSearched(true);

    if (!response.success || !response.data) {
      setResults([]);
      notifications.show({
        color: "red",
        message: response.message || "Search failed",
      });
      return;
    }
    setResults(response.data.items);
  }, [term]);

  const confirmDeletion = useCallback(
    async (request: DeletionRequest) => {
      if (!target) return;
      setSubmitting(true);
      const response = await applyAccountDeletion({
        accountNumber: target.accountNumber,
        ...request,
      });
      setSubmitting(false);

      /*
       * A partial outcome is reported as its own case. Core answers 207 when
       * Viewpoint moved and the member database did not; showing that as a plain
       * failure would invite a retry against an account that is already retired.
       */
      const partial = response.data?.localError;
      if (partial) {
        notifications.show({
          color: "orange",
          autoClose: false,
          title: "Partly applied",
          message: `Viewpoint was updated. The member database was not: ${partial}`,
        });
      } else if (!response.success) {
        notifications.show({
          color: "red",
          message: response.message || "Failed to delete the account",
        });
        return;
      } else {
        notifications.show({
          color: "green",
          /*
           * The two outcomes are named apart.
           *
           * Correcting an existing closure sends no acknowledgement, and a message
           * that read the same either way would leave the operator believing a
           * member was emailed when they were not.
           */
          message: response.data?.updatedExisting
            ? `Account ${target.accountNumber} corrected to ${response.data?.appliedStatus.name} — existing closure updated, no new email sent`
            : `Account ${target.accountNumber} moved to ${response.data?.appliedStatus.name}`,
        });
      }

      setTarget(null);
      setHistoryToken((token) => token + 1);
      // Re-read rather than patching the row locally, so what is on screen is what
      // both systems now hold — including anything the write changed indirectly.
      await runSearch();
    },
    [runSearch, target],
  );

  return (
    <Container fluid px={0}>
      <Stack gap="lg">
        <Stack gap={4}>
          <Group gap="xs">
            <IconUserOff size={22} />
            <Title order={3}>Account Deletion</Title>
            <Badge color="red" variant="light">
              Super admin
            </Badge>
          </Group>
        </Stack>

        <Card className={styles.sectionCard} p="md">
          <Stack gap="sm">
            <Group align="flex-end" gap="sm">
              <TextInput
                flex={1}
                label="Account number or email"
                description="Searched live against Viewpoint. An email can appear on several memberships — all of them are listed."
                placeholder="1199943 or member@example.com"
                value={term}
                onChange={(event) => setTerm(event.currentTarget.value)}
                onKeyDown={(event) => {
                  if (event.key === "Enter") void runSearch();
                }}
                rightSectionWidth={92}
                rightSection={
                  term.trim() ? (
                    <Badge size="sm" variant="light" color="gray">
                      {detectKind(term.trim()) === "email"
                        ? "email"
                        : "account no."}
                    </Badge>
                  ) : null
                }
              />
              <Button
                leftSection={<IconSearch size={16} />}
                loading={searching}
                disabled={!term.trim()}
                onClick={() => void runSearch()}
              >
                Search
              </Button>
            </Group>
          </Stack>
        </Card>

        {searching && <Skeleton height={180} radius="md" />}

        {!searching && searched && results.length === 0 && (
          <Alert color="gray" variant="light">
            No account matched{" "}
            {detectKind(term.trim()) === "email"
              ? "that email"
              : "that account number"}
            .
          </Alert>
        )}

        {!searching &&
          results.map((result) => (
            <AccountCard
              key={result.accountNumber}
              account={result}
              onDelete={() => setTarget(result)}
            />
          ))}

        {/*
          Two tables, answering two different questions.

          "Which accounts are retired right now" is a question about the member
          database, and includes accounts this console never touched. "What has this
          console done" is a log of actions, and includes rounds that have since been
          reversed. Merging them would lose one or the other.
        */}
        <RetiredAccountsTable refreshToken={historyToken} />

        <DeletionHistoryTable refreshToken={historyToken} />
      </Stack>

      <DeleteAccountModal
        opened={Boolean(target)}
        account={target}
        statuses={statuses}
        missingStatusIDs={missingStatusIDs}
        statusesLoading={statusesLoading}
        submitting={submitting}
        templates={templates}
        templatesLoading={templatesLoading}
        config={config}
        onClose={() => setTarget(null)}
        onConfirm={(request) => void confirmDeletion(request)}
      />
    </Container>
  );
};

/**
 * One account, as Viewpoint and the member database each see it.
 *
 * The delete button is disabled when Viewpoint has nothing for the account — either
 * it does not exist there or Viewpoint was unreachable. Both cases mean the write to
 * the system of record cannot be made, so offering the action would only produce a
 * failure at the last step.
 */
const AccountCard: React.FC<{
  account: AccountSearchResult;
  onDelete: () => void;
}> = ({ account, onDelete }) => {
  const localStatuses = [
    ...new Set(account.local.members.map((member) => member.accountStatusName)),
  ];
  const drifted =
    account.viewpoint.found &&
    localStatuses.length > 0 &&
    !localStatuses.includes(account.viewpoint.accountStatus ?? "");

  return (
    <Card className={styles.sectionCard} p="md">
      <Stack gap="md">
        <Group justify="space-between" align="flex-start">
          <Stack gap={4}>
            <Group gap="xs">
              <Text fw={600}>{account.viewpoint.accountName ?? "Unknown name"}</Text>
              <Badge variant="light">{account.accountNumber}</Badge>
              {account.viewpoint.accountType && (
                <Badge variant="light" color="gray">
                  {account.viewpoint.accountType}
                </Badge>
              )}
            </Group>
            {account.viewpoint.joinedDate && (
              <Text size="sm" c="dimmed">
                Joined {formatDate(account.viewpoint.joinedDate)}
              </Text>
            )}
          </Stack>
          <Button
            color="red"
            variant="light"
            leftSection={<IconTrash size={16} />}
            disabled={!account.viewpoint.found}
            onClick={onDelete}
          >
            Delete account
          </Button>
        </Group>

        {/*
          The two systems, side by side and never merged.

          Viewpoint is the status that decides what the account may do; the core
          record is Subito's working copy of it. Showing one combined status would
          hide the case that matters most here — the copy having drifted — so each
          gets its own panel and the drift is called out separately below.
        */}
        <SimpleGrid cols={{ base: 1, sm: 2 }} spacing="sm">
          <Paper withBorder radius="md" p="sm">
            <Stack gap={6}>
              <Text size="xs" tt="uppercase" c="dimmed" fw={600}>
                Viewpoint status
              </Text>
              {account.viewpoint.found ? (
                <Group gap="xs">
                  <Badge
                    size="lg"
                    {...accountStatusTone(account.viewpoint.accountStatus)}
                  >
                    {account.viewpoint.accountStatus ?? "—"}
                  </Badge>
                  {account.viewpoint.accountStatusID !== null && (
                    <Text size="xs" c="dimmed">
                      id {account.viewpoint.accountStatusID}
                    </Text>
                  )}
                </Group>
              ) : (
                <Badge size="lg" color="red" variant="light">
                  not found / unreachable
                </Badge>
              )}
              <Text size="xs" c="dimmed">
                System of record — read live on every search.
              </Text>
            </Stack>
          </Paper>

          <Paper withBorder radius="md" p="sm">
            <Stack gap={6}>
              <Text size="xs" tt="uppercase" c="dimmed" fw={600}>
                Core status
              </Text>
              {localStatuses.length > 0 ? (
                <Group gap="xs">
                  {localStatuses.map((status) => (
                    <Badge key={status} size="lg" {...accountStatusTone(status)}>
                      {status}
                    </Badge>
                  ))}
                </Group>
              ) : (
                <Badge size="lg" color="gray" variant="light">
                  no member record
                </Badge>
              )}
              <Text size="xs" c="dimmed">
                Subito&apos;s copy — what the app enforces until the next sync.
              </Text>
            </Stack>
          </Paper>
        </SimpleGrid>

        {drifted && (
          <Alert
            color="orange"
            variant="light"
            icon={<IconAlertTriangle size={16} />}
          >
            Viewpoint and the console disagree on this account&apos;s status. The last
            sync has not landed; check that before retiring it.
          </Alert>
        )}

        {!account.local.found && account.viewpoint.found && (
          <Alert color="gray" variant="light">
            No member record in the console database, so only Viewpoint will change.
          </Alert>
        )}

        {account.local.members.length > 0 && (
          <Table highlightOnHover>
            <Table.Thead className={styles.tableHead}>
              <Table.Tr>
                <Table.Th className={styles.th}>Member</Table.Th>
                <Table.Th className={styles.th}>Member no.</Table.Th>
                <Table.Th className={styles.th}>Email</Table.Th>
                <Table.Th className={styles.th}>Role</Table.Th>
                <Table.Th className={styles.th}>Login</Table.Th>
              </Table.Tr>
            </Table.Thead>
            <Table.Tbody>
              {account.local.members.map((member) => (
                <Table.Tr key={member.memberID}>
                  <Table.Td>
                    {[member.firstName, member.lastName]
                      .filter(Boolean)
                      .join(" ") || "—"}
                  </Table.Td>
                  <Table.Td>{member.memberNumber}</Table.Td>
                  <Table.Td>{member.email ?? "—"}</Table.Td>
                  <Table.Td>{member.isOwner ? "Owner" : "Contact"}</Table.Td>
                  <Table.Td>
                    <Badge
                      variant="light"
                      color={member.userIsActive ? undefined : "gray"}
                    >
                      {member.userIsActive ? "active" : "inactive"}
                    </Badge>
                  </Table.Td>
                </Table.Tr>
              ))}
            </Table.Tbody>
          </Table>
        )}

        <Text size="sm" c="dimmed">
          {account.local.activeSessions} active session
          {account.local.activeSessions === 1 ? "" : "s"} ·{" "}
          {account.viewpoint.contacts.length} contact
          {account.viewpoint.contacts.length === 1 ? "" : "s"} in Viewpoint
        </Text>
      </Stack>
    </Card>
  );
};

export default AccountDeletionClientPage;
