"use client";

import {
  Alert,
  Anchor,
  Badge,
  Card,
  Center,
  Grid,
  Group,
  Loader,
  SimpleGrid,
  Stack,
  Table,
  Text,
  TextInput,
  Title,
} from "@mantine/core";
import {
  IconAlertTriangle,
  IconArrowLeft,
  IconSearch,
  IconUserQuestion,
} from "@tabler/icons-react";
import { useEffect, useMemo, useState } from "react";
import { Link, useLoaderData, useSearchParams } from "react-router";
import {
  PageArrival,
  PageSection,
} from "@/routes/promo-code-campaigns/_components/motion";
import { PromoThemeShell } from "@/routes/promo-code-campaigns/_components/PromoThemeShell";
import { usePromoTheme } from "@/routes/promo-code-campaigns/_components/appearance";
import {
  StatTiles,
  type StatTileSpec,
} from "@/routes/promo-code-campaigns/_components/StatTiles";
import styles from "@/routes/promo-code-campaigns/_components/promo.module.css";
import TablePaginationFooter from "@/components/TablePaginationFooter";
import { formatRevenue } from "@/lib/export/eventAnalyticsSheets";
import { ExportButton } from "@/lib/export/ExportButton";
import { buildEventAnalyticsSheets } from "@/lib/export/eventAnalyticsSheets";
import { getMemberDetail } from "@/lib/features/event-analytics/query";
import {
  eventTypeLabel,
  type MemberDetailResponse,
} from "@/lib/features/event-analytics/types";
import { useSessionStorageState } from "@/hooks/useSessionStorageState";
import { useEventFilters } from "../../_hooks/useEventFilters";
import MemberTimeline from "./_components/MemberTimeline";
import type { loader } from "./page";

const FUNNEL_ORDER = ["add_to_cart", "booking_hold_initiated", "purchase"];

const funnelRank = (eventType: string) => {
  const i = FUNNEL_ORDER.indexOf(eventType);
  return i === -1 ? FUNNEL_ORDER.length : i;
};

const RECORDS_PAGE_SIZE = 25;

/**
 * Date for display, falling back to the producer's raw string.
 *
 * `occurredAt` is the parsed value and reads better, but it is null whenever
 * parsing fails — and a "—" where Firestore plainly holds a date reads as
 * missing data rather than as a formatting problem. Showing the raw string is
 * uglier and correct.
 */
const fmtDateTime = (record: { occurredAt: string | null; createdAtRaw: string | null }) => {
  if (record.occurredAt) return record.occurredAt.replace("T", " ").slice(0, 19);
  return record.createdAtRaw ?? "—";
};

const fmtDate = (iso: string | null) => (iso ? iso.slice(0, 10) : "—");

/** Aggregate timestamps are already normalised, so they have no raw fallback. */
const fmtIsoDateTime = (iso: string | null) =>
  iso ? iso.replace("T", " ").slice(0, 19) : "—";

const fmtMoney = (revenue: number | null, currency: string | null) => {
  if (revenue === null) return "—";
  const amount = revenue.toLocaleString("en-US", {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });
  return currency ? `${currency} ${amount}` : amount;
};

/** One label/value pair in the profile card. */
function Field({ label, value }: { label: string; value: string | null }) {
  return (
    <Stack gap={0}>
      <Text size="xs" c="dimmed" tt="uppercase" fw={600}>
        {label}
      </Text>
      <Text size="sm" c={value ? undefined : "dimmed"}>
        {value || "—"}
      </Text>
    </Stack>
  );
}

/**
 * Everything known about one member: who they are, from the Updot member DB,
 * and everything they did, from Firestore.
 *
 * The whole history arrives in one response, so the timeline, the per-property
 * and per-source splits and the full record list are all local — searching and
 * paging the records costs nothing and never refetches.
 *
 * The page keeps the list's filters in its own URL, so arriving here from a
 * filtered view shows the same slice rather than silently widening to all time.
 */
export default function MemberDetailClientPage() {
  const { memberId } = useLoaderData<typeof loader>();
  const { filters, filterRows } = useEventFilters();
  const { series } = usePromoTheme();
  const [searchParams] = useSearchParams();

  // Same preference as the list page, so toggling charts off there does not
  // reappear the moment you open a member.
  const [showCharts] = useSessionStorageState("event-analytics:show-charts", true);

  /*
   * Back carries the filters home.
   *
   * The filters live in this page's own query string, so returning to a bare
   * /admin/event-analytics would silently widen the view the user came from and
   * leave them re-applying a date range they had already set.
   */
  const backTo = `/admin/event-analytics${
    searchParams.toString() ? `?${searchParams}` : ""
  }`;

  const [data, setData] = useState<MemberDetailResponse | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const [recordSearch, setRecordSearch] = useState("");
  const [recordPage, setRecordPage] = useState(1);
  const [recordPageSize, setRecordPageSize] = useState(RECORDS_PAGE_SIZE);

  const filterKey = JSON.stringify(filters);

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

  const eventTypes = useMemo(
    () =>
      [...(data?.eventTypes ?? [])].sort(
        (a, b) => funnelRank(a.eventType) - funnelRank(b.eventType),
      ),
    [data?.eventTypes],
  );

  const tiles = useMemo<StatTileSpec[]>(() => {
    const palette = series(Math.max(1, eventTypes.length + 2));
    const summary = data?.summary;
    const member = data?.member;

    // One tile per funnel step, so the member's own drop-off is the first thing
    // on the page, followed by the two totals that summarise it.
    const stepTiles: StatTileSpec[] = eventTypes.map((row, i) => ({
      key: row.eventType,
      label: eventTypeLabel(row.eventType),
      value: row.events,
      color: palette[i].from,
      colorTo: palette[i].to,
      icon: <Text fw={700}>{i + 1}</Text>,
      percent: summary?.totalEvents ? (row.events / summary.totalEvents) * 100 : 0,
      hint: `${row.points.toLocaleString("en-US")} points`,
    }));

    const totals = palette.slice(eventTypes.length);
    return [
      ...stepTiles,
      {
        key: "points",
        label: "Points earned",
        value: summary?.totalPoints ?? 0,
        color: totals[0]?.from ?? "#c2a154",
        colorTo: totals[0]?.to,
        icon: <Text fw={700}>★</Text>,
        hint: `across ${(summary?.totalEvents ?? 0).toLocaleString("en-US")} events`,
      },
      {
        key: "properties",
        label: "Properties",
        value: data?.byProperty.length ?? member?.properties.length ?? 0,
        color: totals[1]?.from ?? "#876328",
        colorTo: totals[1]?.to,
        icon: <Text fw={700}>⌂</Text>,
        hint: data?.byProperty.length
          ? data.byProperty
              .slice(0, 3)
              .map((p) => p.property)
              .join(", ")
          : "No property recorded",
      },
    ];
  }, [data, eventTypes, series]);

  /*
   * Record search runs locally against the full history.
   *
   * The whole set is already here, so a round trip per keystroke would be pure
   * latency for a result the browser can produce instantly.
   */
  const filteredRecords = useMemo(() => {
    const all = data?.records ?? [];
    const needle = recordSearch.trim().toLowerCase();
    if (!needle) return all;
    return all.filter((r) =>
      [
        r.eventType,
        r.source,
        r.propertyName,
        r.experienceName,
        r.memberOffer,
        r.occurredAt,
      ].some((v) => v?.toLowerCase().includes(needle)),
    );
  }, [data?.records, recordSearch]);

  useEffect(() => setRecordPage(1), [recordSearch, filterKey]);

  const pagedRecords = filteredRecords.slice(
    (recordPage - 1) * recordPageSize,
    recordPage * recordPageSize,
  );

  const profile = data?.profile ?? null;
  const displayName = profile?.fullName ?? `Member ${memberId}`;
  const filterNote = filterRows
    .filter((r) => r.label !== "Member ID")
    .map((r) => `${r.label}: ${r.value}`)
    .join(" · ");

  /** Export mirrors the main page's workbook, scoped to this member. */
  const getExportData = async () => {
    const current = data;
    if (!current) throw new Error("Nothing to export yet");
    return buildEventAnalyticsSheets({
      records: current.records,
      members: current.member ? [current.member] : [],
      eventTypes: current.eventTypes,
      summary: current.summary,
      filterRows: [
        { label: "Member", value: `${displayName} (${memberId})` },
        ...filterRows,
      ],
      eventTypeColumns: eventTypes.map((t) => t.eventType),
    });
  };

  return (
    <PromoThemeShell>
      <PageArrival>
        <Stack px={28} py={20} gap="lg">
          {/* Above the title, not beside it: the way back should be the first
              thing read on the page, not something found next to the actions. */}
          <Anchor
            component={Link}
            to={backTo}
            size="sm"
            w="fit-content"
            underline="never"
          >
            <Group gap={4} wrap="nowrap">
              <IconArrowLeft size={15} />
              <span>Back to Event Analytics</span>
            </Group>
          </Anchor>

          <Group justify="space-between" align="flex-start" wrap="wrap">
            <Stack gap={2}>
              <Title order={2}>{displayName}</Title>
              <Text size="sm" c="dimmed">
                Membership number {memberId}
                {filterNote ? ` · ${filterNote}` : ""}
              </Text>
            </Stack>
            <Group gap="sm">
              {data && (
                <ExportButton
                  label="Export member"
                  filename={`event-analytics-member-${memberId}`}
                  section="reports"
                  roleSlug="event-analytics"
                  filtersSummary={`Member ${memberId}${filterNote ? ` · ${filterNote}` : ""}`}
                  getData={getExportData}
                />
              )}
            </Group>
          </Group>

          {error && (
            <Alert color="red" icon={<IconAlertTriangle size={18} />}>
              {error}
            </Alert>
          )}

          {loading && !data ? (
            <Center py="xl">
              <Loader />
            </Center>
          ) : (
            <>
              {/*
               * A member with events but no profile is expected, not an error:
               * the event carries a number the member DB may not know, so say so
               * plainly rather than render a card full of dashes.
               */}
              {!profile && !loading && (
                <Alert color="yellow" icon={<IconUserQuestion size={18} />}>
                  No member record matched membership number {memberId} in the
                  member database. The event figures below are still accurate.
                </Alert>
              )}

              {data?.recordsTruncated && (
                <Alert color="yellow" icon={<IconAlertTriangle size={18} />}>
                  This member has more events than the page reads. The figures
                  below cover the {data.records.length.toLocaleString("en-US")}{" "}
                  most recent.
                </Alert>
              )}

              {profile && (
                <PageSection>
                  <Card className={styles.sectionCard} p="md">
                    <Stack gap="sm">
                      <Group gap="sm" align="center">
                        <Title order={5}>Member details</Title>
                        {profile.accountStatus && (
                          <Badge
                            variant="light"
                            color={
                              profile.accountStatus.toLowerCase() === "active"
                                ? "teal"
                                : "gray"
                            }
                          >
                            {profile.accountStatus}
                          </Badge>
                        )}
                        {profile.accountType && (
                          <Badge variant="outline">{profile.accountType}</Badge>
                        )}
                      </Group>
                      <SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} spacing="md">
                        <Field label="Full name" value={profile.fullName} />
                        <Field
                          label="Membership number"
                          value={profile.membershipNumber}
                        />
                        <Field label="Email" value={profile.email} />
                        <Field label="Mobile" value={profile.mobile} />
                        <Field label="Country" value={profile.country} />
                        <Field label="Nationality" value={profile.nationality} />
                        <Field
                          label="Date of birth"
                          value={fmtDate(profile.dateOfBirth)}
                        />
                        <Field
                          label="Member since"
                          value={fmtDate(profile.memberSince)}
                        />
                        <Field
                          label="Signup promo code"
                          value={profile.signupPromoCode}
                        />
                      </SimpleGrid>
                      {/*
                        * Hand off to the members module rather than growing a
                        * second member page here. It owns the account overview,
                        * contacts, bookings, sessions and logs; this page owns
                        * the event history. Same handoff the promo-code module
                        * makes, including `from`/`fromLabel` so its back link
                        * returns here rather than to the members list.
                        */}
                      <Anchor
                        component={Link}
                        to={`/admin/members/${encodeURIComponent(memberId)}?${new URLSearchParams(
                          {
                            from: `/admin/event-analytics/members/${memberId}${
                              searchParams.toString() ? `?${searchParams}` : ""
                            }`,
                            fromLabel: "Event Analytics",
                          },
                        )}`}
                        size="sm"
                      >
                        Open full member record →
                      </Anchor>
                    </Stack>
                  </Card>
                </PageSection>
              )}

              <PageSection>
                <StatTiles tiles={tiles} loading={loading && !data} />
              </PageSection>

              {showCharts && (
                <PageSection>
                  <MemberTimeline byDay={data?.byDay ?? []} />
                </PageSection>
              )}

              <PageSection>
                <Grid gutter="md">
                  <Grid.Col span={{ base: 12, lg: 4 }}>
                    <Card className={styles.sectionCard} p="md" h="100%">
                      <Stack gap="sm">
                        <Title order={5}>By event type</Title>
                        {eventTypes.length === 0 ? (
                          <Text c="dimmed" size="sm">
                            No events in this range.
                          </Text>
                        ) : (
                          <Table verticalSpacing="xs">
                            <Table.Thead>
                              <Table.Tr>
                                <Table.Th>Event</Table.Th>
                                <Table.Th ta="right">Events</Table.Th>
                                <Table.Th ta="right">Points</Table.Th>
                              </Table.Tr>
                            </Table.Thead>
                            <Table.Tbody>
                              {eventTypes.map((row) => (
                                <Table.Tr key={row.eventType}>
                                  <Table.Td>{eventTypeLabel(row.eventType)}</Table.Td>
                                  <Table.Td ta="right" className={styles.num}>
                                    {row.events.toLocaleString("en-US")}
                                  </Table.Td>
                                  <Table.Td ta="right" className={styles.num}>
                                    {row.points.toLocaleString("en-US")}
                                  </Table.Td>
                                </Table.Tr>
                              ))}
                            </Table.Tbody>
                          </Table>
                        )}
                        <Stack gap={0}>
                          <Text size="xs" c="dimmed" tt="uppercase" fw={600}>
                            Seen
                          </Text>
                          <Text size="sm">
                            {fmtIsoDateTime(data?.summary.firstEventAt ?? null)} →{" "}
                            {fmtIsoDateTime(data?.summary.lastEventAt ?? null)}
                          </Text>
                        </Stack>
                      </Stack>
                    </Card>
                  </Grid.Col>

                  <Grid.Col span={{ base: 12, lg: 4 }}>
                    <Card className={styles.sectionCard} p="md" h="100%">
                      <Stack gap="sm">
                        <Title order={5}>By property</Title>
                        {(data?.byProperty ?? []).length === 0 ? (
                          <Text c="dimmed" size="sm">
                            No property recorded on these events.
                          </Text>
                        ) : (
                          <Table verticalSpacing="xs">
                            <Table.Thead>
                              <Table.Tr>
                                <Table.Th>Property</Table.Th>
                                <Table.Th ta="right">Events</Table.Th>
                                <Table.Th ta="right">Points</Table.Th>
                              </Table.Tr>
                            </Table.Thead>
                            <Table.Tbody>
                              {(data?.byProperty ?? []).map((row) => (
                                <Table.Tr key={row.property}>
                                  <Table.Td>{row.property}</Table.Td>
                                  <Table.Td ta="right" className={styles.num}>
                                    {row.events.toLocaleString("en-US")}
                                  </Table.Td>
                                  <Table.Td ta="right" className={styles.num}>
                                    {row.points.toLocaleString("en-US")}
                                  </Table.Td>
                                </Table.Tr>
                              ))}
                            </Table.Tbody>
                          </Table>
                        )}
                      </Stack>
                    </Card>
                  </Grid.Col>

                  <Grid.Col span={{ base: 12, lg: 4 }}>
                    <Card className={styles.sectionCard} p="md" h="100%">
                      <Stack gap="sm">
                        <Title order={5}>By source</Title>
                        {(data?.bySource ?? []).length === 0 ? (
                          <Text c="dimmed" size="sm">
                            No source recorded on these events.
                          </Text>
                        ) : (
                          <Table verticalSpacing="xs">
                            <Table.Thead>
                              <Table.Tr>
                                <Table.Th>Source</Table.Th>
                                <Table.Th ta="right">Events</Table.Th>
                                <Table.Th ta="right">Points</Table.Th>
                              </Table.Tr>
                            </Table.Thead>
                            <Table.Tbody>
                              {(data?.bySource ?? []).map((row) => (
                                <Table.Tr key={row.source}>
                                  <Table.Td>{row.source}</Table.Td>
                                  <Table.Td ta="right" className={styles.num}>
                                    {row.events.toLocaleString("en-US")}
                                  </Table.Td>
                                  <Table.Td ta="right" className={styles.num}>
                                    {row.points.toLocaleString("en-US")}
                                  </Table.Td>
                                </Table.Tr>
                              ))}
                            </Table.Tbody>
                          </Table>
                        )}
                        {/* Revenue gets a line rather than a card: for this
                            producer it is almost always absent, and an empty
                            card would take space proportional to its value. */}
                        <Stack gap={0}>
                          <Text size="xs" c="dimmed" tt="uppercase" fw={600}>
                            Revenue
                          </Text>
                          <Text size="sm">
                            {formatRevenue(data?.summary.revenueByCurrency ?? [])}
                          </Text>
                        </Stack>
                      </Stack>
                    </Card>
                  </Grid.Col>
                </Grid>
              </PageSection>

              <PageSection>
                <Stack gap="sm">
                  <Group justify="space-between" align="center" wrap="wrap">
                    <Title order={5}>
                      All events ({filteredRecords.length.toLocaleString("en-US")})
                    </Title>
                    <TextInput
                      placeholder="Filter these events"
                      leftSection={<IconSearch size={16} />}
                      w={280}
                      value={recordSearch}
                      onChange={(e) => setRecordSearch(e.currentTarget.value)}
                    />
                  </Group>

                  <Card className={styles.tableCard} p={0}>
                    <Table.ScrollContainer minWidth={860}>
                      <Table highlightOnHover verticalSpacing="sm" horizontalSpacing="md">
                        <Table.Thead className={styles.tableHead}>
                          <Table.Tr>
                            <Table.Th>Created at</Table.Th>
                            <Table.Th>Event</Table.Th>
                            <Table.Th>Source</Table.Th>
                            <Table.Th>Property</Table.Th>
                            <Table.Th>Experience / offer</Table.Th>
                            <Table.Th ta="right">Points</Table.Th>
                            <Table.Th ta="right">Revenue</Table.Th>
                          </Table.Tr>
                        </Table.Thead>
                        <Table.Tbody>
                          {pagedRecords.map((record) => (
                            <Table.Tr key={record.id}>
                              <Table.Td>{fmtDateTime(record)}</Table.Td>
                              <Table.Td>
                                <Badge variant="light" color="gray">
                                  {eventTypeLabel(record.eventType)}
                                </Badge>
                              </Table.Td>
                              <Table.Td>{record.source ?? "—"}</Table.Td>
                              <Table.Td>{record.propertyName ?? "—"}</Table.Td>
                              <Table.Td>
                                {record.experienceName ?? record.memberOffer ?? "—"}
                              </Table.Td>
                              <Table.Td ta="right" className={styles.num}>
                                {record.points === null
                                  ? "—"
                                  : record.points.toLocaleString("en-US")}
                              </Table.Td>
                              <Table.Td ta="right">
                                {fmtMoney(record.revenue, record.currency)}
                              </Table.Td>
                            </Table.Tr>
                          ))}
                          {pagedRecords.length === 0 && (
                            <Table.Tr>
                              <Table.Td colSpan={7}>
                                <Text c="dimmed" ta="center" py="md">
                                  {recordSearch
                                    ? "No events match that search."
                                    : "No events for this member in the selected range."}
                                </Text>
                              </Table.Td>
                            </Table.Tr>
                          )}
                        </Table.Tbody>
                      </Table>
                    </Table.ScrollContainer>
                  </Card>

                  <TablePaginationFooter
                    total={filteredRecords.length}
                    page={recordPage}
                    totalPages={Math.max(
                      1,
                      Math.ceil(filteredRecords.length / recordPageSize),
                    )}
                    pageSize={recordPageSize}
                    onPageChange={setRecordPage}
                    onPageSizeChange={(size) => {
                      setRecordPageSize(size);
                      setRecordPage(1);
                    }}
                  />
                </Stack>
              </PageSection>
            </>
          )}
        </Stack>
      </PageArrival>
    </PromoThemeShell>
  );
}
