"use client";

import { Group, Stack, Switch, Tabs, Title, Tooltip } from "@mantine/core";
import {
  IconChartAreaLine,
  IconListDetails,
  IconTags,
  IconUsers,
} from "@tabler/icons-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useNavigate } from "react-router";
import {
  PageArrival,
  PageSection,
} from "@/routes/promo-code-campaigns/_components/motion";
import { PromoThemeShell } from "@/routes/promo-code-campaigns/_components/PromoThemeShell";
import { ExportButton } from "@/lib/export/ExportButton";
import { buildEventAnalyticsSheets } from "@/lib/export/eventAnalyticsSheets";
import { MAX_EXPORT_ROWS, withTimeout } from "@/lib/export/exportLimits";
import {
  getEventRecords,
  getEventTypeAnalytics,
  getMemberAnalytics,
  getEventSummary,
} from "@/lib/features/event-analytics/query";
import type {
  EventRecord,
  MemberAnalyticsRow,
  EventTypeAnalyticsResponse,
  MemberAnalyticsResponse,
  SummaryResponse,
} from "@/lib/features/event-analytics/types";
import EventAnalytics from "./_components/EventAnalytics";
import EventRecords from "./_components/EventRecords";
import MemberAnalytics, { type MemberSort } from "./_components/MemberAnalytics";
import MemberDashboard from "./_components/MemberDashboard";
import SummaryCards from "./_components/SummaryCards";
import { useSessionStorageState } from "@/hooks/useSessionStorageState";
import { useEventFilterUI } from "./_hooks/useEventFilterUI";
import { useEventFilters } from "./_hooks/useEventFilters";
import tabStyles from "./_components/eventAnalytics.module.css";

const RECORDS_PAGE_SIZE = 25;
/** Page size for the export's own fetch loop — larger than the UI's, fewer round trips. */
const EXPORT_PAGE_SIZE = 200;

/**
 * Booking funnel order. Any event type the producer adds that is not listed
 * here still appears, ordered after these.
 */
const FUNNEL_ORDER = ["add_to_cart", "booking_hold_initiated", "purchase"];

const byFunnelOrder = (a: string, b: string) => {
  const rank = (t: string) => {
    const i = FUNNEL_ORDER.indexOf(t);
    return i === -1 ? FUNNEL_ORDER.length : i;
  };
  return rank(a) - rank(b) || a.localeCompare(b);
};

export default function EventAnalyticsClientPage() {
  const navigate = useNavigate();
  const { filters, setFilters, resetFilters, filterRows, filtersSummary } =
    useEventFilters();

  const [summary, setSummary] = useState<SummaryResponse | null>(null);
  const [members, setMembers] = useState<MemberAnalyticsResponse | null>(null);
  const [eventTypes, setEventTypes] = useState<EventTypeAnalyticsResponse | null>(null);
  const [records, setRecords] = useState<EventRecord[]>([]);
  const [recordsTotal, setRecordsTotal] = useState<number | null>(null);
  const [recordsHasMore, setRecordsHasMore] = useState(false);

  const [loadingSummary, setLoadingSummary] = useState(true);
  const [loadingMembers, setLoadingMembers] = useState(true);
  const [loadingEventTypes, setLoadingEventTypes] = useState(true);
  const [loadingRecords, setLoadingRecords] = useState(true);

  /*
   * Charts on or off, for the whole module.
   *
   * Session-scoped rather than in the URL: it is a reading preference, not part
   * of what the page is showing, so it should follow the user between tabs and
   * into the member page without becoming part of a shared link.
   */
  const [showCharts, setShowCharts] = useSessionStorageState(
    "event-analytics:show-charts",
    true,
  );

  /** Controlled so the event tab can hand the user to the member tab. */
  const [activeTab, setActiveTab] = useState<string | null>("members");

  /*
   * Leaderboard rows for the member dashboard.
   *
   * Fetched separately from the table because the table is sorted and paged by
   * whatever the user chose — asking it for "the top members by points" would
   * mean overriding their sort. The aggregate behind both is Redis-cached, so
   * the second call is cheap.
   */
  const [topMembers, setTopMembers] = useState<MemberAnalyticsRow[]>([]);

  const [memberSearch, setMemberSearch] = useState("");
  const [selectedMembers, setSelectedMembers] = useState<string[]>([]);
  const [selectedEventTypes, setSelectedEventTypes] = useState<string[]>([]);

  const [memberPage, setMemberPage] = useState(1);
  const [memberPageSize, setMemberPageSize] = useState(25);
  const [memberSort, setMemberSort] = useState<MemberSort>({
    sortBy: "events",
    sortDir: "desc",
  });

  // Firestore cursors, one per page boundary already visited. Index 0 is the
  // first page (no cursor); going back pops the stack rather than re-querying
  // forward from the start.
  const [recordCursors, setRecordCursors] = useState<(string | undefined)[]>([undefined]);
  const [recordPage, setRecordPage] = useState(1);

  const filterKey = JSON.stringify(filters);

  // Changing a filter invalidates every page position — leaving them would show
  // page 7 of a result set that now has two pages.
  useEffect(() => {
    setMemberPage(1);
    setRecordCursors([undefined]);
    setRecordPage(1);
  }, [filterKey]);

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

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

  useEffect(() => {
    const controller = new AbortController();
    setLoadingMembers(true);
    void (async () => {
      const res = await getMemberAnalytics(
        filters,
        {
          page: memberPage,
          pageSize: memberPageSize,
          memberSearch: memberSearch.trim() || undefined,
          ...memberSort,
        },
        { signal: controller.signal },
      );
      if (controller.signal.aborted) return;
      if (res?.success) setMembers(res.data);
      setLoadingMembers(false);
    })();
    return () => controller.abort();
  }, [
    filterKey,
    memberPage,
    memberPageSize,
    memberSearch,
    memberSort.sortBy,
    memberSort.sortDir,
  ]);

  useEffect(() => {
    const controller = new AbortController();
    void (async () => {
      const res = await getMemberAnalytics(
        filters,
        { page: 1, pageSize: 8, sortBy: "points", sortDir: "desc" },
        { signal: controller.signal },
      );
      if (controller.signal.aborted) return;
      if (res?.success) setTopMembers(res.data.rows);
    })();
    return () => controller.abort();
  }, [filterKey]);

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

  useEffect(() => {
    const controller = new AbortController();
    setLoadingRecords(true);
    void (async () => {
      const res = await getEventRecords(
        filters,
        { pageSize: RECORDS_PAGE_SIZE, cursor: recordCursors[recordPage - 1] },
        { signal: controller.signal },
      );
      if (controller.signal.aborted) return;
      if (res?.success) {
        setRecords(res.data.records);
        setRecordsTotal(res.data.total);
        setRecordsHasMore(res.data.hasMore);
        // Remember this page's onward cursor so Next can use it.
        if (res.data.nextCursor) {
          setRecordCursors((prev) => {
            const next = [...prev];
            next[recordPage] = res.data.nextCursor ?? undefined;
            return next;
          });
        }
      }
      setLoadingRecords(false);
    })();
    return () => controller.abort();
  }, [filterKey, recordPage]);

  /*
   * Columns for the member funnel: the types currently filtered to, or all of
   * them when nothing is filtered. Driven by the filter rather than by what the
   * page of members happens to contain, so the columns stay put as you page.
   */
  const eventTypeColumns = useMemo(() => {
    const types = filters.eventTypes.length
      ? filters.eventTypes
      : (summary?.availableEventTypes ?? []);
    return [...types].sort(byFunnelOrder);
  }, [filters.eventTypes, summary?.availableEventTypes]);

  const filterUI = useEventFilterUI({
    filters,
    onChange: setFilters,
    onReset: resetFilters,
    availableEventTypes: summary?.availableEventTypes ?? [],
    availableCurrencies: summary?.availableCurrencies ?? [],
    availableProperties: summary?.availableProperties ?? [],
    availableSources: summary?.availableSources ?? [],
  });

  const toggleEventType = useCallback(
    (eventType: string) => {
      const active = filters.eventTypes.includes(eventType);
      setFilters({
        eventTypes: active
          ? filters.eventTypes.filter((t) => t !== eventType)
          : [...filters.eventTypes, eventType],
      });
    },
    [filters.eventTypes, setFilters],
  );

  /*
   * Everything the export reads, behind a ref.
   *
   * ExportButton runs `getData` well after the click, so the callback must see
   * the state as it is at that moment — but it must not change identity on every
   * render either, or the button would re-mount mid-export. A ref gives both.
   *
   * The aggregates are already on screen and reused as-is; only the detail rows
   * are re-fetched, since the table only ever holds one page.
   */
  const latest = useRef({
    summary,
    members,
    eventTypes,
    filterRows,
    filters,
    eventTypeColumns,
    selectedMembers,
    selectedEventTypes,
  });
  latest.current = {
    summary,
    members,
    eventTypes,
    filterRows,
    filters,
    eventTypeColumns,
    selectedMembers,
    selectedEventTypes,
  };

  const getExportData = useCallback(
    async (signal: AbortSignal) => {
      const all: EventRecord[] = [];
      let cursor: string | undefined;

      // Page until exhausted, bailing out on cancel or once past the row cap —
      // ExportButton rejects an oversized export anyway, so there is no point
      // paying for the rest of the reads first.
      for (;;) {
        if (signal.aborted) throw new DOMException("Export cancelled", "AbortError");

        const res = await withTimeout(
          getEventRecords(
            latest.current.filters,
            { pageSize: EXPORT_PAGE_SIZE, cursor },
            { signal },
          ),
        );
        if (!res?.success) {
          throw new Error(res?.message || "Could not fetch event records");
        }

        all.push(...res.data.records);
        if (!res.data.hasMore || !res.data.nextCursor) break;
        if (all.length > MAX_EXPORT_ROWS) break;
        cursor = res.data.nextCursor;
      }

      const current = latest.current;
      return buildEventAnalyticsSheets({
        records: all,
        members: current.members?.rows ?? [],
        eventTypes: current.eventTypes?.rows ?? [],
        summary: current.summary!.summary,
        filterRows: current.filterRows,
        eventTypeColumns: current.eventTypeColumns,
        // Selections drive the per-member and per-event sheets. Empty means the
        // workbook is just the roll-ups, which is the old behaviour.
        perMemberIds: current.selectedMembers,
        perEventTypes: current.selectedEventTypes,
      });
    },
    [],
  );

  const dateSuffix = `${filters.from ?? "all"}-to-${filters.to ?? "now"}`;

  /*
   * The button says what the workbook will contain.
   *
   * With members or events ticked the export gains a sheet per selection, which
   * is a different file from the unselected case — the label is the only place
   * that difference is visible before the download starts.
   */
  const exportLabel =
    selectedMembers.length && selectedEventTypes.length
      ? `Export ${selectedMembers.length} members + ${selectedEventTypes.length} events`
      : selectedMembers.length
        ? `Export ${selectedMembers.length} member${selectedMembers.length === 1 ? "" : "s"}`
        : selectedEventTypes.length
          ? `Export ${selectedEventTypes.length} event${selectedEventTypes.length === 1 ? "" : "s"}`
          : "Export Event Analytics";

  return (
    <PromoThemeShell>
      <PageArrival>
      <Stack px={28} py={20} gap="lg">
        <Group justify="space-between" align="center">
          <Title order={2}>Event Analytics</Title>
          <Group gap="sm">
            <Tooltip
              label={showCharts ? "Hide all charts" : "Show all charts"}
              withArrow
            >
              <Switch
                size="sm"
                checked={showCharts}
                onChange={(e) => setShowCharts(e.currentTarget.checked)}
                label={
                  <Group gap={4} wrap="nowrap">
                    <IconChartAreaLine size={15} />
                    <span>Charts</span>
                  </Group>
                }
              />
            </Tooltip>
            {filterUI.trigger}
            {summary && (
              <ExportButton
                label={exportLabel}
                filename={`event-analytics-${dateSuffix}`}
                section="reports"
                roleSlug="event-analytics"
                filtersSummary={filtersSummary}
                getData={getExportData}
              />
            )}
          </Group>
        </Group>

        {filterUI.chips}
        {filterUI.drawer}

        <PageSection>
          <SummaryCards summary={summary?.summary ?? null} loading={loadingSummary} />
        </PageSection>

        <Tabs value={activeTab} onChange={setActiveTab} keepMounted={false}>
          <Tabs.List className={tabStyles.tabList}>
            <Tabs.Tab
              value="members"
              className={tabStyles.tab}
              leftSection={<IconUsers size={16} />}
            >
              Member-wise
            </Tabs.Tab>
            <Tabs.Tab
              value="events"
              className={tabStyles.tab}
              leftSection={<IconTags size={16} />}
            >
              Event-wise
            </Tabs.Tab>
            <Tabs.Tab
              value="records"
              className={tabStyles.tab}
              leftSection={<IconListDetails size={16} />}
            >
              Detailed records
            </Tabs.Tab>
          </Tabs.List>

          <Tabs.Panel value="members" pt="md">
            {showCharts && (
              <PageSection>
                <MemberDashboard
                  summary={summary?.summary ?? null}
                  eventTypes={eventTypes?.rows ?? []}
                  byDay={summary?.byDay ?? []}
                  byProperty={summary?.byProperty ?? []}
                  bySource={summary?.bySource ?? []}
                  topMembers={topMembers}
                  loading={loadingSummary || loadingEventTypes}
                  onSelectMember={(memberId) =>
                    navigate(
                      `/admin/event-analytics/members/${encodeURIComponent(memberId)}${
                        window.location.search
                      }`,
                    )
                  }
                />
              </PageSection>
            )}
            <PageSection>
            <MemberAnalytics
              data={members}
              loading={loadingMembers}
              sort={memberSort}
              onSort={setMemberSort}
              page={memberPage}
              pageSize={memberPageSize}
              onPageChange={setMemberPage}
              onPageSizeChange={(size) => {
                setMemberPageSize(size);
                setMemberPage(1);
              }}
              onSelectMember={(memberId) => setFilters({ memberId })}
              eventTypeColumns={eventTypeColumns}
              search={memberSearch}
              onSearchChange={setMemberSearch}
              selected={selectedMembers}
              onSelectedChange={setSelectedMembers}
            />
            </PageSection>
          </Tabs.Panel>

          <Tabs.Panel value="events" pt="md">
            <PageSection>
            <EventAnalytics
              data={eventTypes}
              loading={loadingEventTypes}
              activeEventTypes={filters.eventTypes}
              onToggleEventType={toggleEventType}
              showCharts={showCharts}
              selected={selectedEventTypes}
              onSelectedChange={setSelectedEventTypes}
              onOpenEvent={(eventType) =>
                // Carries the current filters so the event page opens on the
                // same slice the user was looking at.
                navigate(
                  `/admin/event-analytics/events/${encodeURIComponent(eventType)}${window.location.search}`,
                )
              }
            />
            </PageSection>
          </Tabs.Panel>

          <Tabs.Panel value="records" pt="md">
            <PageSection>
            <EventRecords
              records={records}
              loading={loadingRecords}
              total={recordsTotal}
              hasMore={recordsHasMore}
              page={recordPage}
              onNextPage={() => setRecordPage((p) => p + 1)}
              onPrevPage={() => setRecordPage((p) => Math.max(1, p - 1))}
            />
            </PageSection>
          </Tabs.Panel>
        </Tabs>
      </Stack>
      </PageArrival>
    </PromoThemeShell>
  );
}
