"use client";

/**
 * The confirmation step for retiring an account.
 *
 * Everything irreversible about the action is stated on this screen rather than
 * implied by the button: which account, which status it moves to, what else stops
 * working, and that Viewpoint is written first.
 */

import {
  Alert,
  Badge,
  Button,
  Divider,
  Group,
  List,
  Modal,
  Select,
  Stack,
  Text,
  Textarea,
} from "@mantine/core";
import { IconAlertTriangle } from "@tabler/icons-react";
import { useEffect, useMemo, useState } from "react";
import type {
  AccountSearchResult,
  DeletionConfig,
  DeletionRegion,
  DeletionStatus,
} from "@/lib/features/account-deletion/query";

export interface DeletionRequest {
  statusID: string;
  reason: string;
  killLogin: boolean;
  revokeSessions: boolean;
  softDelete: boolean;
  region: DeletionRegion;
  /** Empty means "send the region's own letter". */
  noticeTemplateID: string;
  followupTemplateID: string;
}

/** One EDM template, reduced to what the picker needs. */
export interface TemplateOption {
  id: string;
  name: string;
}

interface Props {
  opened: boolean;
  account: AccountSearchResult | null;
  statuses: DeletionStatus[];
  /** Expected Viewpoint status ids the member database did not have. */
  missingStatusIDs: string[];
  statusesLoading: boolean;
  submitting: boolean;
  templates: TemplateOption[];
  templatesLoading: boolean;
  config: DeletionConfig | null;
  onClose: () => void;
  onConfirm: (request: DeletionRequest) => void;
}

const MIN_REASON = 10;

/*
 * The value that means "the letter this region sends".
 *
 * An empty string rather than null, because Mantine's Select treats null as "nothing
 * selected" and would show the placeholder — which reads as an unanswered required
 * field, when in fact it is the answer nearly every deletion wants.
 */
const DEFAULT_VALUE = "";

/*
 * The five regions, held here as well as in core.
 *
 * The picker used to take its options from the fetched catalogue, which meant that
 * until `/config` came back — or at all, if it failed — the Region select rendered
 * with no options and nothing selected, on a required field the operator could not
 * fill. The set is fixed by what letters exist, so the console can state it and be
 * right; only the template *names* need the catalogue, and those degrade to "the
 * regional letter" on their own.
 */
const REGION_OPTIONS: Array<{ value: DeletionRegion; label: string }> = [
  { value: "europe", label: "Europe" },
  { value: "india", label: "India" },
  { value: "indonesia", label: "Indonesia" },
  { value: "uk", label: "United Kingdom" },
  // Last, because it is the fallback rather than a peer of the others.
  { value: "others", label: "Rest of world" },
];

/**
 * The picker's options: the default first, then every template by name.
 *
 * The default's own label carries the template's name when one is configured, so the
 * operator can see what they are accepting instead of having to look it up.
 */
const templateOptions = (
  templates: TemplateOption[],
  regionalDefault: { id: string; name: string } | undefined,
) => [
    {
      value: DEFAULT_VALUE,
      /*
       * The default option always works, named or not.
       *
       * When the catalogue has not arrived the name is unknown, but the behaviour is
       * not — core resolves an empty override to the region's own letter. "None for
       * this region" read as broken and invited the operator to hand-pick a template
       * they had no reason to override.
       */
      label: regionalDefault
        ? `Regional letter — ${regionalDefault.name}`
        : "Regional letter for this region",
    },
    ...templates
      // The regional letter is already the first option; listing it again as a
      // manual pick would mean two entries that do the same thing.
      .filter((template) => template.id !== regionalDefault?.id)
      .map((template) => ({ value: template.id, label: template.name })),
  ];

const DeleteAccountModal: React.FC<Props> = ({
  opened,
  account,
  statuses,
  missingStatusIDs,
  statusesLoading,
  submitting,
  templates,
  templatesLoading,
  config,
  onClose,
  onConfirm,
}) => {
  const [statusID, setStatusID] = useState<string | null>(null);
  const [reason, setReason] = useState("");
  // Empty string is the default choice, not an unset field — see DEFAULT_VALUE.
  const [noticeTemplate, setNoticeTemplate] = useState("");
  const [followupTemplate, setFollowupTemplate] = useState("");
  const [region, setRegion] = useState<DeletionRegion>("others");

  // Reset per account, not per open: reopening on a different account with the
  // previous one's reason and status still filled in is how the wrong thing gets
  // applied to the wrong membership.
  useEffect(() => {
    setStatusID(null);
    setReason("");
    setNoticeTemplate("");
    setFollowupTemplate("");
    // Seeded from what core detected off the account's country. Reset with the rest
    // so an override made for one account cannot follow the operator to the next.
    setRegion(account?.region ?? "others");
  }, [account?.accountNumber, account?.region, opened]);

  const options = useMemo(
    () =>
      statuses.map((status) => ({
        value: status.id,
        label: status.applicable
          ? status.name
          : `${status.name} — no Viewpoint id mapped`,
        disabled: !status.applicable,
      })),
    [statuses],
  );

  const selected = statuses.find((status) => status.id === statusID) ?? null;

  /*
   * Warn only when the default is the active choice and there is no default.
   *
   * Picking a template explicitly makes a missing environment default irrelevant, so
   * the warning would only be noise then.
   */
  const regionEntry =
    config?.regions.find((entry) => entry.region === region) ?? null;

  /*
   * True when the region was not detected and the operator has not corrected it.
   *
   * "Rest of world" is a legitimate answer for a member outside the named regions,
   * but it is also what an account with no country on file falls back to — and those
   * two are not the same thing. Worth pointing out, because the letters differ on
   * what the member is told about erasure of their data.
   */
  const regionUndetected =
    region === "others" && account?.regionSource === "fallback";

  /*
   * Whether this will correct an existing closure instead of opening one.
   *
   * Read off the account's current status being one of the deletion statuses, which
   * is exactly the set this dialog offers — so if the account already holds one of
   * them, it has already been retired.
   */
  const isCorrection = statuses.some(
    (status) =>
      status.extAccountStatusID != null &&
      Number(status.extAccountStatusID) === account?.viewpoint.accountStatusID,
  );

  /*
   * The status the account is already in cannot be applied to it.
   *
   * Core refuses this too, but offering it and then failing wastes a round trip and
   * reads as a bug. Compared on the Viewpoint id rather than the name, which is the
   * value both ends agree on.
   */
  const alreadyInStatus =
    selected?.extAccountStatusID != null &&
    account?.viewpoint.accountStatusID != null &&
    Number(selected.extAccountStatusID) === account.viewpoint.accountStatusID;

  const canSubmit =
    Boolean(account) &&
    Boolean(selected?.applicable) &&
    !alreadyInStatus &&
    reason.trim().length >= MIN_REASON &&
    !submitting;

  if (!account) return null;

  const memberCount = account.local.members.length;
  const localStatuses = [
    ...new Set(account.local.members.map((member) => member.accountStatusName)),
  ];

  return (
    <Modal
      opened={opened}
      onClose={onClose}
      title={`Delete account ${account.accountNumber}`}
      size="lg"
      centered
    >
      <Stack gap="md">
        <Stack gap={6}>
          <Text size="sm" c="dimmed">
            Account
          </Text>
          <Group gap="xs">
            <Text fw={600}>{account.viewpoint.accountName ?? "—"}</Text>
            <Badge variant="light">{account.accountNumber}</Badge>
          </Group>
          {/*
            Both current statuses, restated at the point of confirmation.

            The operator saw them on the card, but the card is behind this dialog and
            the two can disagree. Repeating them here is what makes "move to X" a
            decision rather than a guess about what X is replacing.
          */}
          <Group gap="xs">
            <Text size="sm" c="dimmed">
              Viewpoint:
            </Text>
            <Badge variant="light" color="gray">
              {account.viewpoint.accountStatus ?? "—"}
            </Badge>
            <Text size="sm" c="dimmed">
              Core:
            </Text>
            {localStatuses.length > 0 ? (
              localStatuses.map((status) => (
                <Badge key={status} variant="light" color="gray">
                  {status}
                </Badge>
              ))
            ) : (
              <Badge variant="light" color="gray">
                no member record
              </Badge>
            )}
          </Group>
        </Stack>

        <Divider label="Emails to the member" labelPosition="left" />

        {/*
          Region first, because it decides both letters.

          What a UK or European member must be told about erasure of their personal
          data differs from what an Indian or Indonesian member is told, so the pair
          is chosen by region rather than picked freely. Detected from the account's
          country and shown here so it can be corrected — sending the wrong region's
          letter is a compliance problem, not a cosmetic one.
        */}
        <Select
          label="Region"
          description={
            account.regionSource === "charge-centre"
              ? `From the charge centre: ${account.viewpoint.chargeCentreName}`
              : account.regionSource === "country"
                ? `From the billing country: ${account.viewpoint.country}`
                : "Neither the charge centre nor a country identified a region — correct this if the member is in a named region."
          }
          data={REGION_OPTIONS}
          value={region}
          onChange={(value) => {
            setRegion((value as DeletionRegion) ?? "others");
            /*
             * Overrides are dropped when the region changes.
             *
             * An override was chosen against the old region's letters. Keeping it
             * would send one region's acknowledgement with another's follow-up,
             * which is the exact mismatch the region picker exists to prevent.
             */
            setNoticeTemplate(DEFAULT_VALUE);
            setFollowupTemplate(DEFAULT_VALUE);
          }}
          withAsterisk
        />

        {regionUndetected && (
          <Alert color="yellow" variant="light">
            <Text size="sm">
              The charge centre{" "}
              {account.viewpoint.chargeCentreName
                ? `(${account.viewpoint.chargeCentreName})`
                : "is not set"}{" "}
              and the billing country identified no region, so this fell back to{" "}
              <strong>Rest of world</strong>. That is correct for a member outside the
              named regions — but if they are in the UK, Europe, India or Indonesia,
              set the region before deleting.
            </Text>
          </Alert>
        )}

        <Select
          label="Email sent now"
          description="The acknowledgement the member receives when the account is retired."
          data={templateOptions(templates, regionEntry?.acknowledgement)}
          value={noticeTemplate}
          onChange={(value) => setNoticeTemplate(value ?? DEFAULT_VALUE)}
          disabled={templatesLoading}
          searchable
          allowDeselect={false}
        />

        <Select
          label={`Email sent after ${config?.followupDays ?? 30} days`}
          description={`The data-deletion confirmation, sent ${config?.followupDays ?? 30} days after retirement unless the account is reactivated first.`}
          data={templateOptions(templates, regionEntry?.confirmation)}
          value={followupTemplate}
          onChange={(value) => setFollowupTemplate(value ?? DEFAULT_VALUE)}
          disabled={templatesLoading}
          searchable
          allowDeselect={false}
        />

        <Divider label="Status" labelPosition="left" />

        {missingStatusIDs.length > 0 && (
          <Alert color="yellow" variant="light">
            <Text size="sm">
              The member database has no status mapped to Viewpoint id{" "}
              {missingStatusIDs.join(" or ")}, so it is not in the list below. Check
              `member_account_statuses` — a short list is not the same as a complete
              one.
            </Text>
          </Alert>
        )}

        <Select
          label="Account status"
          placeholder={statusesLoading ? "Loading statuses…" : "Select a status"}
          description="Invalid, or the expiry / cancellation status for Subito — whichever your process calls for."
          data={options}
          value={statusID}
          onChange={setStatusID}
          disabled={statusesLoading}
          searchable
          withAsterisk
          error={
            alreadyInStatus
              ? `This account is already ${selected?.name}. Pick the other status, or close this dialog.`
              : null
          }
        />

        <Textarea
          label="Reason"
          description="Stored with the activity log entry for this action."
          placeholder="Why is this account being retired?"
          minRows={3}
          autosize
          value={reason}
          onChange={(event) => setReason(event.currentTarget.value)}
          error={
            reason.length > 0 && reason.trim().length < MIN_REASON
              ? `At least ${MIN_REASON} characters`
              : null
          }
          withAsterisk
        />

        {/*
          What retiring an account does, stated rather than asked.

          These were three checkboxes. They were not a real choice — an account that
          is closed but can still sign in, or whose sessions stay live, is a state
          nobody wants and the wrong answer to pick by accident. So the behaviour is
          fixed and simply described here.

          Soft-delete stays off: hiding the member from the console's lists is a
          separate decision from closing the account, and hiding a record makes the
          closure harder to find afterwards.
        */}
        <Text size="sm" c="dimmed">
          Retiring this account also disables Subito sign-in for{" "}
          {memberCount === 1 ? "the member" : `all ${memberCount} members`} on it
          {account.local.activeSessions > 0
            ? ` and drops ${account.local.activeSessions} live session${account.local.activeSessions === 1 ? "" : "s"} immediately`
            : ""}
          . The member records stay in the console and keep their bookings.
        </Text>

        {memberCount > 1 && (
          <Alert color="orange" variant="light">
            <Text size="sm">
              This membership carries {memberCount} member records. All of them are
              retired together, because the account is the unit being retired:
            </Text>
            <List size="sm" mt={6}>
              {account.local.members.map((member) => (
                <List.Item key={member.memberID}>
                  {[member.firstName, member.lastName].filter(Boolean).join(" ") ||
                    member.memberNumber}
                  {member.email ? ` — ${member.email}` : ""}
                </List.Item>
              ))}
            </List>
          </Alert>
        )}

        <Group justify="flex-end" mt="xs">
          <Button variant="default" onClick={onClose} disabled={submitting}>
            Cancel
          </Button>
          <Button
            color="red"
            loading={submitting}
            disabled={!canSubmit}
            onClick={() =>
              onConfirm({
                statusID: statusID!,
                reason: reason.trim(),
                /*
                 * Fixed, not chosen. See the note beside the summary above: a
                 * closed account that can still sign in is not a state anyone
                 * wants, so it is not offered as an option.
                 */
                killLogin: true,
                revokeSessions: true,
                softDelete: false,
                region,
                noticeTemplateID: noticeTemplate,
                followupTemplateID: followupTemplate,
              })
            }
          >
            Delete account
          </Button>
        </Group>
      </Stack>
    </Modal>
  );
};

export default DeleteAccountModal;
