"use client";

import {
  Badge,
  Card,
  Center,
  Group,
  Loader,
  Select,
  Stack,
  Table,
  Text,
  TextInput,
  Title,
  Tooltip,
} from "@mantine/core";
import { DatePickerInput } from "@mantine/dates";
import { useDebouncedCallback } from "@mantine/hooks";
import { IconSearch, IconX } from "@tabler/icons-react";
import { useEffect, useMemo, useState } from "react";
import { useLoaderData } from "react-router";
import TablePaginationFooter from "@/components/TablePaginationFooter";
import { ExportButton } from "@/lib/export/ExportButton";
import type { ExportColumn } from "@/lib/export/exportXlsx";
import {
  PageArrival,
  PageSection,
} from "@/routes/promo-code-campaigns/_components/motion";
import styles from "@/routes/promo-code-campaigns/_components/promo.module.css";
import {
  getReferralInviteAnalytics,
  getReferralInvites,
} from "@/lib/features/member-referral-invites/query";
import {
  EMPTY_INVITE_FILTERS,
  referrerLabel,
  type ReferralInviteAnalytics,
  type ReferralInviteFilters,
  type ReferralInviteRow,
  type ReferralInvitesResponse,
} from "@/lib/features/member-referral-invites/types";
import InviteAnalytics from "./_components/InviteAnalytics";
import type { loader } from "./page";

const PAGE_SIZE = 25;
/** Server cap on pageSize; the export walks pages at this size. */
const EXPORT_PAGE_SIZE = 200;

const fmtDateTime = (iso: string | null) =>
  iso ? iso.replace("T", " ").slice(0, 19) : "—";

/** `yyyy-mm-dd` for the API; core widens a date-only `to` to end of day. */
const toDateParam = (value: unknown): string | null => {
  if (!value) return null;
  if (typeof value === "string") {
    if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
    const parsed = new Date(value);
    return Number.isNaN(parsed.getTime()) ? null : toDateParam(parsed);
  }
  if (value instanceof Date) {
    if (Number.isNaN(value.getTime())) return null;
    const y = value.getFullYear();
    const m = String(value.getMonth() + 1).padStart(2, "0");
    const d = String(value.getDate()).padStart(2, "0");
    return `${y}-${m}-${d}`;
  }
  return null;
};

/**
 * Referral invites — who was emailed, by whom, and whether they joined.
 *
 * Complements the overview page, which reports on the referral promo codes.
 * This is the invitation record itself: `member_referral_invites` joined to the
 * redemption that proves acceptance.
 */
export default function ReferralInvitesClientPage() {
  const { accessScope } = useLoaderData<typeof loader>();

  const [filters, setFilters] = useState<ReferralInviteFilters>(EMPTY_INVITE_FILTERS);
  const [search, setSearch] = useState("");
  const [data, setData] = useState<ReferralInvitesResponse | null>(null);
  const [analytics, setAnalytics] = useState<ReferralInviteAnalytics | null>(null);
  const [loading, setLoading] = useState(true);
  const [loadingAnalytics, setLoadingAnalytics] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(PAGE_SIZE);

  const filterKey = JSON.stringify(filters);

  // A narrowed list has its own page count, so page 4 of the old one is
  // meaningless against the new.
  useEffect(() => setPage(1), [filterKey, pageSize]);

  const pushSearch = useDebouncedCallback(
    (value: string) => setFilters((f) => ({ ...f, search: value || null })),
    400,
  );

  useEffect(() => {
    const controller = new AbortController();
    setLoading(true);
    void (async () => {
      const res = await getReferralInvites(
        filters,
        { page, pageSize },
        { signal: controller.signal },
      );
      if (controller.signal.aborted) return;
      if (res?.success) setData(res.data);
      else setError(res?.message || "Could not load referral invites.");
      setLoading(false);
    })();
    return () => controller.abort();
  }, [filterKey, page, pageSize]);

  useEffect(() => {
    const controller = new AbortController();
    setLoadingAnalytics(true);
    void (async () => {
      const res = await getReferralInviteAnalytics(filters, {
        signal: controller.signal,
      });
      if (controller.signal.aborted) return;
      if (res?.success) setAnalytics(res.data);
      setLoadingAnalytics(false);
    })();
    return () => controller.abort();
  }, [filterKey]);

  const sourceOptions = useMemo(
    () => (analytics?.bySource ?? []).map((s) => s.source),
    [analytics?.bySource],
  );

  const hasFilters =
    Boolean(filters.from) ||
    Boolean(filters.to) ||
    Boolean(filters.search) ||
    Boolean(filters.source) ||
    Boolean(filters.status) ||
    Boolean(filters.referrerMembershipNumber);

  /** Walks every page so the file is the whole filtered set, not the page on screen. */
  const getExportData = async (signal: AbortSignal) => {
    const all: ReferralInviteRow[] = [];
    for (let p = 1; ; p += 1) {
      if (signal.aborted) throw new DOMException("Export cancelled", "AbortError");
      const res = await getReferralInvites(
        filters,
        { page: p, pageSize: EXPORT_PAGE_SIZE },
        { signal },
      );
      if (!res?.success) throw new Error(res?.message || "Could not fetch invites");
      all.push(...res.data.rows);
      if (p >= res.data.totalPages) break;
    }

    return {
      sheetName: "Referral Sign-ups",
      columns: ([
        { key: "#", label: "#", width: 6 },
        { key: "Email", label: "Email", width: 30 },
        { key: "Captured At", label: "Captured At", width: 22 },
        { key: "Registered", label: "Registered", width: 12 },
        { key: "Registered At", label: "Registered At", width: 22 },
        { key: "Already A Member", label: "Already A Member", width: 18 },
        { key: "Successful Referral", label: "Successful Referral", width: 20 },
        { key: "Succeeded At", label: "Succeeded At", width: 22 },
        { key: "Consent", label: "Consent", width: 10 },
        { key: "Source", label: "Source", width: 16 },
        { key: "Referrer", label: "Referrer", width: 26 },
        { key: "Referrer Membership No", label: "Referrer Membership No", width: 22 },
        { key: "Referrer Email", label: "Referrer Email", width: 30 },
        { key: "Referrer Country", label: "Referrer Country", width: 18 },
        { key: "Promo Code ID", label: "Promo Code ID", width: 26 },
        { key: "Referred Reward At", label: "Referred Reward At", width: 22 },
        { key: "Referrer Reward At", label: "Referrer Reward At", width: 22 },
      ] as ExportColumn<Record<string, unknown>>[]),
      rows: all.map((r, i): Record<string, unknown> => ({
        "#": i + 1,
        Email: r.email,
        "Captured At": r.capturedAt ?? "",
        // "Yes"/"No" rather than TRUE/FALSE: this is read by people, and Excel
        // renders a real boolean as uppercase which reads like shouting.
        Registered: r.registered ? "Yes" : "No",
        "Registered At": r.registeredAt ?? "",
        "Already A Member": r.alreadyMember ? "Yes" : "No",
        "Successful Referral": r.credited ? "Yes" : "No",
        "Succeeded At": r.creditedAt ?? "",
        Consent: r.consent ? "Yes" : "No",
        Source: r.source ?? "",
        Referrer: referrerLabel(r),
        "Referrer Membership No": r.referrerMembershipNumber ?? "",
        "Referrer Email": r.referrerEmail ?? "",
        "Referrer Country": r.referrerCountry ?? "",
        "Promo Code ID": r.promoCodeId ?? "",
        "Referred Reward At": r.referredRewardIssuedAt ?? "",
        "Referrer Reward At": r.referrerRewardIssuedAt ?? "",
      })),
    };
  };

  const rows = data?.rows ?? [];

  return (
    <PageArrival>
      <Stack px={28} py={20} gap="lg">
        <Group justify="space-between" align="flex-start" wrap="wrap">
          <Stack gap={2}>
            <Title order={3}>Referral Sign-ups</Title>
          </Stack>
          {accessScope.update && data && (
            <ExportButton
              label="Export Sign-ups"
              filename="member-referral-signups"
              section="memberReferrals"
              getData={getExportData}
              filtersSummary={
                hasFilters ? "Filtered view" : "All referral sign-ups"
              }
            />
          )}
        </Group>

        <PageSection>
          <InviteAnalytics
            data={analytics}
            loading={loadingAnalytics}
            onSelectReferrer={(membershipNumber) =>
              setFilters((f) => ({ ...f, referrerMembershipNumber: membershipNumber }))
            }
          />
        </PageSection>

        <PageSection>
          <Stack gap="sm">
            <Group gap="sm" align="flex-end" wrap="wrap">
              <TextInput
                label="Search"
                placeholder="Email, referrer, membership no."
                leftSection={<IconSearch size={16} />}
                w={300}
                value={search}
                onChange={(e) => {
                  setSearch(e.currentTarget.value);
                  pushSearch(e.currentTarget.value);
                }}
              />
              <DatePickerInput
                type="range"
                label="Captured between"
                placeholder="All time"
                w={240}
                clearable
                value={[filters.from, filters.to] as any}
                onChange={(range: any) => {
                  const [from, to] = (range ?? [null, null]) as [unknown, unknown];
                  setFilters((f) => ({
                    ...f,
                    from: toDateParam(from),
                    to: toDateParam(to),
                  }));
                }}
              />
              <Select
                label="Outcome"
                placeholder="All"
                w={150}
                clearable
                data={[
                  { value: "registered", label: "Registered" },
                  { value: "unregistered", label: "Not registered" },
                  { value: "credited", label: "Successful referral" },
                ]}
                value={filters.status}
                onChange={(v) =>
                  setFilters((f) => ({
                    ...f,
                    status:
                      (v as "registered" | "unregistered" | "credited" | null) ?? null,
                  }))
                }
              />
              <Select
                label="Source"
                placeholder="All sources"
                w={170}
                clearable
                searchable
                data={sourceOptions}
                value={filters.source}
                onChange={(v) => setFilters((f) => ({ ...f, source: v }))}
              />
              {filters.referrerMembershipNumber && (
                <Badge
                  size="lg"
                  variant="light"
                  rightSection={
                    <IconX
                      size={13}
                      style={{ cursor: "pointer" }}
                      onClick={() =>
                        setFilters((f) => ({ ...f, referrerMembershipNumber: null }))
                      }
                    />
                  }
                >
                  Referrer {filters.referrerMembershipNumber}
                </Badge>
              )}
            </Group>

            {error && (
              <Text c="red" size="sm">
                {error}
              </Text>
            )}

            {loading && !data ? (
              <Center py="xl">
                <Loader size="sm" />
              </Center>
            ) : (
              <>
                <Card className={styles.tableCard} p={0}>
                  <Table.ScrollContainer minWidth={1000}>
                    <Table highlightOnHover verticalSpacing="sm" horizontalSpacing="md">
                      <Table.Thead className={styles.tableHead}>
                        <Table.Tr>
                          <Table.Th>Email</Table.Th>
                          <Table.Th>Referred by</Table.Th>
                          <Table.Th>Captured at</Table.Th>
                          <Table.Th>Outcome</Table.Th>
                          <Table.Th>Registered at</Table.Th>
                          <Table.Th>Succeeded at</Table.Th>
                          <Table.Th>Source</Table.Th>
                          <Table.Th>Consent</Table.Th>
                          <Table.Th>Rewards</Table.Th>
                        </Table.Tr>
                      </Table.Thead>
                      <Table.Tbody>
                        {rows.map((row) => (
                          <Table.Tr key={row.id}>
                            <Table.Td>
                              <Text size="sm">{row.email}</Text>
                            </Table.Td>
                            <Table.Td>
                              <Stack gap={0}>
                                <Text size="sm" fw={600} lineClamp={1}>
                                  {referrerLabel(row)}
                                </Text>
                                <Text size="xs" c="dimmed" lineClamp={1}>
                                  {[row.referrerMembershipNumber, row.referrerCountry]
                                    .filter(Boolean)
                                    .join(" · ") || "—"}
                                </Text>
                              </Stack>
                            </Table.Td>
                            <Table.Td>{fmtDateTime(row.capturedAt)}</Table.Td>
                            <Table.Td>
                              {/* An invite whose address was already a member is
                                  accepted by the rule but did not bring anyone
                                  in, so it is flagged rather than silently
                                  counted alongside genuine signups. */}
                              <Tooltip
                                withArrow
                                disabled={!row.alreadyMember}
                                label="This address already belonged to a member before they used the link — no new member was gained"
                              >
                                <Badge
                                  variant="light"
                                  color={
                                    !row.registered
                                      ? "gray"
                                      : row.alreadyMember
                                        ? "yellow"
                                        : "teal"
                                  }
                                  style={{ cursor: row.alreadyMember ? "help" : undefined }}
                                >
                                  {!row.registered
                                    ? "Not registered"
                                    : row.alreadyMember
                                      ? "Already a member"
                                      : "Registered"}
                                </Badge>
                              </Tooltip>
                            </Table.Td>
                            <Table.Td>{fmtDateTime(row.registeredAt)}</Table.Td>
                            <Table.Td>
                              <Text size="sm" c={row.credited ? undefined : "dimmed"}>
                                {row.credited ? fmtDateTime(row.creditedAt) : "—"}
                              </Text>
                            </Table.Td>
                            <Table.Td>{row.source ?? "—"}</Table.Td>
                            <Table.Td>
                              <Text size="sm" c={row.consent ? undefined : "dimmed"}>
                                {row.consent ? "Yes" : "No"}
                              </Text>
                            </Table.Td>
                            <Table.Td>
                              {/* Two rewards per redemption; neither implies the
                                  other, so both are shown rather than merged. */}
                              <Group gap={4}>
                                <Tooltip label="Referred member's reward" withArrow>
                                  <Badge
                                    size="xs"
                                    variant={row.referredRewardIssuedAt ? "filled" : "outline"}
                                    color={row.referredRewardIssuedAt ? "teal" : "gray"}
                                  >
                                    Invitee
                                  </Badge>
                                </Tooltip>
                                <Tooltip label="Referrer's reward" withArrow>
                                  <Badge
                                    size="xs"
                                    variant={row.referrerRewardIssuedAt ? "filled" : "outline"}
                                    color={row.referrerRewardIssuedAt ? "teal" : "gray"}
                                  >
                                    Referrer
                                  </Badge>
                                </Tooltip>
                              </Group>
                            </Table.Td>
                          </Table.Tr>
                        ))}
                        {rows.length === 0 && (
                          <Table.Tr>
                            <Table.Td colSpan={8}>
                              <Text c="dimmed" ta="center" py="md">
                                No referral sign-ups match these filters.
                              </Text>
                            </Table.Td>
                          </Table.Tr>
                        )}
                      </Table.Tbody>
                    </Table>
                  </Table.ScrollContainer>
                </Card>

                <TablePaginationFooter
                  total={data?.total ?? 0}
                  page={page}
                  totalPages={data?.totalPages ?? 1}
                  pageSize={pageSize}
                  onPageChange={setPage}
                  onPageSizeChange={(size) => {
                    setPageSize(size);
                    setPage(1);
                  }}
                />
              </>
            )}
          </Stack>
        </PageSection>
      </Stack>
    </PageArrival>
  );
}
