"use client";

import { useCoreFetcher } from "@/lib/features/useCoreFetcher";
import type {
  AccessListCampaign,
  CampaignPromoCodeMapRow,
  KnownPromoCode,
} from "@/lib/features/access-list/query";
import type { AccessScope } from "@/lib/features/types";
import { PromoAccessSelector } from "@/lib/features/promo-access-roles/PromoAccessSelector";
import type { AdminUser } from "@/lib/features/users/types";
import {
  EXPORT_SECTION_ORDER,
  type ExportMode,
  type ExportSectionKey,
  type UserAccessList,
} from "@/lib/features/access-list/types";
import { EXPORT_SECTIONS } from "@/lib/features/access-list/exportSections";
import {
  Badge,
  Button,
  Checkbox,
  Flex,
  Grid,
  Group,
  Paper,
  Menu,
  ScrollArea,
  Select,
  SegmentedControl,
  Stack,
  Text,
  ThemeIcon,
  TextInput,
  Title,
} from "@mantine/core";
import {
  IconFileSpreadsheet,
  IconSearch,
} from "@tabler/icons-react";
import { useMemo, useState } from "react";
import { toast } from "sonner";
import { useEffect } from "react";
import { useParams } from "react-router";
import {
  applyPromoAccessRole,
  listPromoAccessRoles,
  type PromoAccessRoleSummary,
} from "@/lib/features/promo-access-roles/query";

/**
 * The three export modes, described once.
 *
 * Drives the summary badges, the "Set all" menu and each row's helper text, so a
 * wording or colour change lands in all three places at once.
 */
const MODE_SUMMARY: Array<{
  mode: ExportMode;
  label: string;
  color: string;
  hint: string;
}> = [
  {
    mode: "download",
    label: "Download",
    color: "blue",
    hint: "Downloads the file directly.",
  },
  {
    mode: "request",
    label: "Request",
    color: "orange",
    hint: "Needs super-admin approval, then emailed to them.",
  },
  {
    mode: "none",
    label: "None",
    color: "gray",
    hint: "No export at all — not even a request.",
  },
];

const AccessListDetailClientPage: React.FC<{
  user?: AdminUser;
  promoCodes: KnownPromoCode[];
  campaigns: AccessListCampaign[];
  campaignPromoCodeMap: CampaignPromoCodeMapRow[];
  access: UserAccessList;
  accessScope: AccessScope;
}> = ({ user, promoCodes, campaigns, campaignPromoCodeMap, access, accessScope }) => {
  const [selectedPromoCodeIds, setSelectedPromoCodeIds] = useState<Set<string>>(
    new Set(access.promoCodeIds),
  );
  const [selectedCampaignIds, setSelectedCampaignIds] = useState<Set<string>>(
    new Set(access.campaignIds),
  );
  const { trigger, isLoading } = useCoreFetcher(
    "update-user-access-list",
    "put",
    {
      onSuccess: () => {
        toast.success("Access list updated successfully");
      },
      onError: (error) => {
        toast.error((error as Error)?.message ?? "Failed to update access list");
      },
    },
  );

  /*
   * Export settings as a mode per section, not four booleans.
   *
   * A boolean could only say yes or no; three states are needed — download
   * directly, submit for approval, or no export at all. Seeded from the modes the
   * API returns, falling back to the legacy booleans where `false` meant "no direct
   * download", which is `request` here.
   */
  const seedModes = (): Record<ExportSectionKey, ExportMode> => {
    const fromApi = access.exportModes ?? {};
    const legacy: Record<ExportSectionKey, boolean> = {
      campaigns: access.canExportCampaigns,
      promo_codes: access.canExportPromoCodes,
      member_referrals: access.canExportMemberReferrals,
      reports: access.canExportReports,
      /*
       * Neither has a legacy boolean of its own — both used to ride on Campaigns,
       * so for rows written before the modes existed that boolean is exactly what
       * governed them. Falling back to it reproduces the old behaviour rather
       * than inventing a permission.
       */
      bookings: access.canExportCampaigns,
      promo_code_members: access.canExportCampaigns,
    };
    return Object.fromEntries(
      EXPORT_SECTION_ORDER.map((k) => [
        k,
        fromApi[k] ?? (legacy[k] ? "download" : "request"),
      ]),
    ) as Record<ExportSectionKey, ExportMode>;
  };
  const [exportModes, setExportModes] =
    useState<Record<ExportSectionKey, ExportMode>>(seedModes);
  const setMode = (key: ExportSectionKey, mode: ExportMode) =>
    setExportModes((prev) => ({ ...prev, [key]: mode }));

  /*
   * The campaign/promo-code picking logic used to live here.
   *
   * It now lives in PromoAccessSelector, shared with the promo access role
   * editor, so the auto-grant rule exists once rather than in two copies that
   * could drift. Only the two selected sets stay here, since this page is what
   * saves them.
   */

  // Shared with the role editor, so the two lists can't drift and every section
  // carries the pages it governs. See lib/features/access-list/exportSections.
  const exportToggles = EXPORT_SECTIONS;


  /*
   * Promo access roles.
   *
   * Roles are created and edited on their own page; this page only wraps a member
   * in one. Applying copies the role's campaigns, promo codes and export modes onto
   * this member, whose settings then stay editable below — so a member can be
   * adjusted afterwards without affecting anyone else sharing the role.
   */
  // The console user this page edits; the apply endpoint is keyed on it.
  const { userId = "" } = useParams();

  const [roles, setRoles] = useState<PromoAccessRoleSummary[]>([]);
  const [roleId, setRoleId] = useState<string | null>(null);
  const [roleBusy, setRoleBusy] = useState(false);

  useEffect(() => {
    let cancelled = false;
    void listPromoAccessRoles().then((res) => {
      if (cancelled || !res.success || !res.data) return;
      setRoles(res.data);
    });
    return () => {
      cancelled = true;
    };
  }, []);

  const wrapInRole = async (mode: "merge" | "replace") => {
    if (!roleId) return;
    setRoleBusy(true);
    const res = await applyPromoAccessRole(userId, { roleId, mode });
    setRoleBusy(false);
    if (res.success) {
      toast.success(res.message ?? "Role applied");
      // Reload so the pickers and modes below show what was actually stored,
      // rather than a local guess at what the merge produced.
      window.location.reload();
    } else {
      toast.error(res.message ?? "Could not apply the role");
    }
  };

  const handleUpdate = () => {
    void trigger({
      promoCodeIds: Array.from(selectedPromoCodeIds),
      campaignIds: Array.from(selectedCampaignIds),
      // Modes only — core writes the matching legacy boolean itself, so the two
      // representations cannot drift apart.
      exportModes,
    });
  };

  return (
    <Stack bg="grey.0" mih="100vh" p="sm">
      <Stack gap={8} bg="white.0" p="sm" bdrs="sm">
        <Flex justify="space-between" align="flex-start" wrap="wrap" gap="sm">
          <Stack gap={4}>
            <Title order={6}>Promocode Access List</Title>
            <Text size="sm" c="dimmed">
              {user
                ? `${user.first_name} ${user.last_name ?? ""} (${user.email})`
                : ""}
            </Text>
          </Stack>

          {accessScope.update && (
            <Group gap={6} align="flex-end" wrap="wrap">
              {/* Roles are created and edited on their own page; here they are
                  only applied to this member. */}
              <Select
                size="xs"
                w={220}
                placeholder={
                  roles.length ? "Choose a promo role…" : "No roles created yet"
                }
                data={roles.map((r) => ({
                  value: r.id,
                  label: `${r.name} (${r.campaign_count} campaigns, ${r.promo_code_count} codes)`,
                }))}
                value={roleId}
                onChange={setRoleId}
                searchable
                clearable
                disabled={roles.length === 0}
                nothingFoundMessage="No roles"
              />
              <Menu position="bottom-end" withArrow shadow="md">
                <Menu.Target>
                  <Button
                    size="compact-sm"
                    variant="light"
                    disabled={!roleId || roleBusy}
                    loading={roleBusy}
                  >
                    Apply role
                  </Button>
                </Menu.Target>
                <Menu.Dropdown>
                  <Menu.Item onClick={() => void wrapInRole("merge")}>
                    Add role access (keeps current)
                  </Menu.Item>
                  {/* Flagged: this is the only action that can remove access. */}
                  <Menu.Item
                    color="orange"
                    onClick={() => void wrapInRole("replace")}
                  >
                    Replace with role access
                  </Menu.Item>
                </Menu.Dropdown>
              </Menu>
            </Group>
          )}
        </Flex>
        <Text size="xs" c="dimmed">
          Applying a role copies its access to this member. Their settings below
          stay editable, and later edits to the role do not change them.
        </Text>
      </Stack>

      {/*
       * The same selector the promo access role editor uses.
       *
       * This page used to carry its own copy of the campaign/code lists, including
       * the auto-grant rule. Two copies of that rule would drift, and a drift there
       * silently grants or revokes access — so it now lives in one component.
       */}
      <PromoAccessSelector
        promoCodes={promoCodes}
        campaigns={campaigns}
        campaignPromoCodeMap={campaignPromoCodeMap}
        selectedCampaignIds={selectedCampaignIds}
        selectedPromoCodeIds={selectedPromoCodeIds}
        disabled={!accessScope.update}
        onChange={({ campaignIds, promoCodeIds }) => {
          setSelectedCampaignIds(campaignIds);
          setSelectedPromoCodeIds(promoCodeIds);
        }}
      />

      <Stack gap={12} bg="white.0" p="md" bdrs="sm">
        <Flex justify="space-between" align="flex-start" wrap="wrap" gap={8}>
          <Stack gap={2}>
            <Group gap={8}>
              <IconFileSpreadsheet size={18} />
              <Title order={6}>Export Permissions</Title>
            </Group>
            {/* Copy rewritten for three states — it still described the old
                on/off switch, which no longer exists. */}
            <Text size="xs" c="dimmed">
              Applies to this user only. Everything defaults to Download.
            </Text>
          </Stack>

          <Group gap={6} wrap="wrap">
            {/*
             * A count per mode, not "N/M enabled".
             *
             * With three states "enabled" was ambiguous — it counted Request and
             * Download together and told you nothing about which. Zero-count
             * modes are dropped so the summary stays short.
             */}
            {MODE_SUMMARY.map(({ mode, label, color }) => {
              const n = exportToggles.filter(
                (t) => exportModes[t.key] === mode,
              ).length;
              if (n === 0) return null;
              return (
                <Badge key={mode} variant="light" color={color} size="sm">
                  {n} {label}
                </Badge>
              );
            })}
            {accessScope.update && (
              <Menu position="bottom-end" withArrow shadow="md">
                <Menu.Target>
                  <Button variant="subtle" size="compact-xs">
                    Set all…
                  </Button>
                </Menu.Target>
                <Menu.Dropdown>
                  {/* Configuring a new user usually means the same answer for
                      every section; five separate clicks was busywork. */}
                  {MODE_SUMMARY.map(({ mode, label }) => (
                    <Menu.Item
                      key={mode}
                      onClick={() =>
                        setExportModes(
                          Object.fromEntries(
                            EXPORT_SECTION_ORDER.map((k) => [k, mode]),
                          ) as Record<ExportSectionKey, ExportMode>,
                        )
                      }
                    >
                      All {label}
                    </Menu.Item>
                  ))}
                </Menu.Dropdown>
              </Menu>
            )}
          </Group>
        </Flex>

        {/*
         * One row per section rather than a card grid.
         *
         * Five cards in a four-column grid left an orphan on its own row, and a
         * three-option control squeezed into a quarter-width card was cramped.
         * Rows also align every control on one axis, so all five settings can be
         * read in a single vertical scan.
         */}
        <Stack gap={6}>
          {exportToggles.map(({ key, label, pages, personalData, Icon }) => {
            const mode = exportModes[key];
            const meta =
              MODE_SUMMARY.find((m) => m.mode === mode) ?? MODE_SUMMARY[0];
            return (
              <Paper key={key} withBorder radius="md" p="xs">
                <Flex justify="space-between" align="center" gap="sm" wrap="wrap">
                  <Group gap={10} wrap="nowrap" style={{ minWidth: 0 }}>
                    <ThemeIcon
                      variant="light"
                      color={meta.color}
                      size="md"
                      radius="md"
                    >
                      <Icon size={16} />
                    </ThemeIcon>
                    <Stack gap={0} style={{ minWidth: 0 }}>
                      <Group gap={6} wrap="nowrap">
                        <Text size="sm" fw={500}>
                          {label}
                        </Text>
                        {/* Flagged because it changes what granting this costs:
                            these sheets carry member names, emails and phone
                            numbers, the others only campaign metadata. */}
                        {personalData && (
                          <Badge variant="light" color="grape" size="xs">
                            personal data
                          </Badge>
                        )}
                      </Group>
                      {/* Which Excel buttons this row governs. A section name on
                          its own didn't say — three exports sit behind Campaigns
                          and two behind Reports. */}
                      <Text size="xs" c="dimmed" lineClamp={1}>
                        {pages}
                      </Text>
                      {/* What the current setting actually does, rather than a
                          legend the reader has to map back themselves. */}
                      <Text size="xs" c="dimmed">
                        {meta.hint}
                      </Text>
                    </Stack>
                  </Group>
                  <SegmentedControl
                    size="xs"
                    value={mode}
                    onChange={(v) => setMode(key, v as ExportMode)}
                    // Read-only for a viewer without update rights, instead of
                    // letting them change a control that cannot be saved.
                    disabled={!accessScope.update}
                    data={MODE_SUMMARY.map((m) => ({
                      value: m.mode,
                      label: m.label,
                    }))}
                    style={{ flexShrink: 0 }}
                  />
                </Flex>
              </Paper>
            );
          })}
        </Stack>
      </Stack>

      {accessScope.update && (
        <Flex justify="end" mt={20}>
          <Button
            variant="filled"
            bg="blue.1"
            c="blue.9"
            loading={isLoading}
            onClick={handleUpdate}
          >
            Update
          </Button>
        </Flex>
      )}
    </Stack>
  );
};

export default AccessListDetailClientPage;
