"use client";

import {
  Alert,
  Anchor,
  Badge,
  Card,
  Center,
  Group,
  Loader,
  Progress,
  SimpleGrid,
  Stack,
  Table,
  Text,
  TextInput,
  Title,
} from "@mantine/core";
import {
  IconAlertTriangle,
  IconArrowLeft,
  IconSearch,
} from "@tabler/icons-react";
import { useEffect, useMemo, useState } from "react";
import { Link, useLoaderData, useSearchParams } from "react-router";
import TablePaginationFooter from "@/components/TablePaginationFooter";
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 styles from "@/routes/promo-code-campaigns/_components/promo.module.css";
import EventExportButton from "../../_components/EventExportButton";
import { formatRevenue } from "@/lib/export/eventAnalyticsSheets";
import {
  buildEventAnalyticsSheets,
} from "@/lib/export/eventAnalyticsSheets";
import { MAX_EXPORT_ROWS, withTimeout } from "@/lib/export/exportLimits";
import {
  getEventRecords,
  getEventSummary,
  getJourneys,
  getMemberAnalytics,
} from "@/lib/features/event-analytics/query";
import {
  eventTypeLabel,
  memberDisplayName,
  type EventRecord,
  type JourneysResponse,
  type MemberAnalyticsResponse,
  type SourceFunnel,
  type SummaryResponse,
} from "@/lib/features/event-analytics/types";
import JourneyTable, {
  type JourneyOutcome,
} from "../../_components/JourneyTable";
import { useEventFilters } from "../../_hooks/useEventFilters";
import type { loader } from "./page";

const PAGE_SIZE = 25;
const EXPORT_PAGE_SIZE = 200;

const fmtInt = (n: number) => n.toLocaleString("en-US");
const fmtDate = (iso: string | null) => (iso ? iso.slice(0, 10) : "—");

/** Hours as something readable — "2.5 h" is worse than "2h 30m" at a glance. */
function formatElapsed(hours: number | null): string {
  if (hours === null) return "—";
  if (hours < 1) return `${Math.round(hours * 60)}m`;
  if (hours < 48) {
    const h = Math.floor(hours);
    const m = Math.round((hours - h) * 60);
    return m ? `${h}h ${m}m` : `${h}h`;
  }
  return `${(hours / 24).toFixed(1)} days`;
}

function Stat({
  label,
  value,
  hint,
}: {
  label: string;
  value: string;
  hint?: string;
}) {
  return (
    <Card className={styles.sectionCard} p="md">
      <Stack gap={0}>
        <Text size="xs" c="dimmed" tt="uppercase" fw={600}>
          {label}
        </Text>
        <Text fz={26} fw={700} lh={1.2}>
          {value}
        </Text>
        {hint && (
          <Text size="xs" c="dimmed">
            {hint}
          </Text>
        )}
      </Stack>
    </Card>
  );
}

/**
 * One acquisition channel, end to end.
 *
 * No new endpoint: the campaign filter already narrows by journey, so the
 * existing summary, member and record calls scoped to this `utm_source` return
 * exactly this channel's carts, holds and purchases. That also guarantees the
 * figures here agree with the main page rather than being a second computation of them.
 */
export default function CampaignDetailClientPage() {
  const { utmSource, accessScope } = useLoaderData<typeof loader>();
  const { filters, filterRows } = useEventFilters();
  const [searchParams] = useSearchParams();
  const { series } = usePromoTheme();

  // This page is about one channel, so it overrides whatever arrived in the URL.
  const scoped = useMemo(
    () => ({ ...filters, utmSource }),
    [filters, utmSource],
  );

  const [summary, setSummary] = useState<SummaryResponse | null>(null);
  const [members, setMembers] = useState<MemberAnalyticsResponse | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [search, setSearch] = useState("");
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(PAGE_SIZE);

  const [journeys, setJourneys] = useState<JourneysResponse | null>(null);
  const [journeysLoading, setJourneysLoading] = useState(true);
  const [journeyOutcome, setJourneyOutcome] = useState<JourneyOutcome | null>(null);
  const [journeyPage, setJourneyPage] = useState(1);
  const [journeyPageSize, setJourneyPageSize] = useState(PAGE_SIZE);

  const scopeKey = JSON.stringify(scoped);

  useEffect(() => setPage(1), [scopeKey, search, pageSize]);
  useEffect(() => setJourneyPage(1), [scopeKey, journeyOutcome, journeyPageSize]);

  useEffect(() => {
    const controller = new AbortController();
    setJourneysLoading(true);
    void (async () => {
      const res = await getJourneys(
        scoped,
        {
          page: journeyPage,
          pageSize: journeyPageSize,
          outcome: journeyOutcome ?? undefined,
        },
        { signal: controller.signal },
      );
      if (controller.signal.aborted) return;
      if (res?.success) setJourneys(res.data);
      setJourneysLoading(false);
    })();
    return () => controller.abort();
  }, [scopeKey, journeyPage, journeyPageSize, journeyOutcome]);

  useEffect(() => {
    const controller = new AbortController();
    void (async () => {
      const res = await getEventSummary(scoped, { signal: controller.signal });
      if (controller.signal.aborted) return;
      if (res?.success) setSummary(res.data);
      else setError(res?.message || "Could not load this campaign.");
    })();
    return () => controller.abort();
  }, [scopeKey]);

  useEffect(() => {
    const controller = new AbortController();
    setLoading(true);
    void (async () => {
      const res = await getMemberAnalytics(
        scoped,
        {
          page,
          pageSize,
          memberSearch: search.trim() || undefined,
          sortBy: "events",
          sortDir: "desc",
        },
        { signal: controller.signal },
      );
      if (controller.signal.aborted) return;
      if (res?.success) setMembers(res.data);
      setLoading(false);
    })();
    return () => controller.abort();
  }, [scopeKey, page, pageSize, search]);

  /** This channel's row out of the summary, so the numbers match the main page. */
  const channel: SourceFunnel | null = useMemo(
    () => summary?.funnelByUtmSource.find((r) => r.key === utmSource) ?? null,
    [summary?.funnelByUtmSource, utmSource],
  );

  const mediums = useMemo(
    () => summary?.funnelByUtmMedium ?? [],
    [summary?.funnelByUtmMedium],
  );

  const backTo = `/admin/event-analytics${
    searchParams.toString() ? `?${searchParams}` : ""
  }`;

  const getExportData = async (signal: AbortSignal) => {
    const all: EventRecord[] = [];
    let cursor: string | undefined;
    for (;;) {
      if (signal.aborted) throw new DOMException("Export cancelled", "AbortError");
      const res = await withTimeout(
        getEventRecords(scoped, { pageSize: EXPORT_PAGE_SIZE, cursor }, { signal }),
      );
      if (!res?.success) throw new Error(res?.message || "Could not fetch 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;
    }

    return buildEventAnalyticsSheets({
      records: all,
      members: members?.rows ?? [],
      eventTypes: [],
      summary: summary!.summary,
      filterRows: [{ label: "Campaign source", value: utmSource }, ...filterRows],
      eventTypeColumns: summary?.availableEventTypes ?? [],
    });
  };

  const [cartColor, holdColor, purchaseColor] = series(3);
  const rows = members?.rows ?? [];

  return (
    <PromoThemeShell>
      <PageArrival>
        <Stack px={28} py={20} gap="lg">
          <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}>
              <Group gap="sm">
                <Title order={2}>{utmSource}</Title>
                <Badge variant="light" color="gray">
                  utm_source
                </Badge>
              </Group>
              <Text size="sm" c="dimmed">
                Journeys that began on a link tagged {utmSource}
                {filterRows.length
                  ? ` · ${filterRows.map((r) => `${r.label}: ${r.value}`).join(" · ")}`
                  : ""}
              </Text>
            </Stack>
            {summary && (
              <EventExportButton
                label="Export campaign"
                filename={`event-analytics-campaign-${utmSource}`}
                filtersSummary={`Campaign ${utmSource}`}
                getData={getExportData}
                canExport={accessScope.update}
              />
            )}
          </Group>

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

          {!summary ? (
            <Center py="xl">
              <Loader />
            </Center>
          ) : !channel ? (
            <Alert color="yellow" icon={<IconAlertTriangle size={18} />}>
              No journeys from {utmSource} in this date range. Widen the dates to
              see its history.
            </Alert>
          ) : (
            <>
              <PageSection>
                <SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }}>
                  {/* Bookings first: it is the number this page exists to answer. */}
                  <Stat
                    label="Bookings"
                    value={fmtInt(channel.purchased)}
                    hint={`${channel.conversionPct}% of ${fmtInt(channel.journeys)} journeys`}
                  />
                  <Stat
                    label="Journeys"
                    value={fmtInt(channel.journeys)}
                    hint={`${fmtInt(channel.members)} distinct member${channel.members === 1 ? "" : "s"}`}
                  />
                  <Stat
                    label="Revenue"
                    value={formatRevenue(channel.revenueByCurrency)}
                    hint={
                      channel.revenueByCurrency.length
                        ? "Per currency, not converted"
                        : "No purchase carried an amount"
                    }
                  />
                  <Stat
                    label="Time to book"
                    value={formatElapsed(channel.medianHoursToPurchase)}
                    hint={`median · ${fmtInt(channel.points)} points earned`}
                  />
                </SimpleGrid>
              </PageSection>

              <PageSection>
                <Card className={styles.sectionCard} p="md">
                  <Stack gap="sm">
                    <Stack gap={0}>
                      <Title order={5}>Where these journeys ended</Title>
                      <Text size="xs" c="dimmed">
                        Every journey is in exactly one of the three.
                      </Text>
                    </Stack>

                    <Progress.Root size="xl" radius="sm">
                      <Progress.Section
                        value={(channel.purchased / channel.journeys) * 100}
                        color={purchaseColor.from}
                      >
                        <Progress.Label>Booked</Progress.Label>
                      </Progress.Section>
                      <Progress.Section
                        value={(channel.stoppedAtHold / channel.journeys) * 100}
                        color={holdColor.from}
                      >
                        <Progress.Label>Held</Progress.Label>
                      </Progress.Section>
                      <Progress.Section
                        value={(channel.stoppedAtCart / channel.journeys) * 100}
                        color={cartColor.from}
                      >
                        <Progress.Label>In cart</Progress.Label>
                      </Progress.Section>
                    </Progress.Root>

                    <SimpleGrid cols={{ base: 1, sm: 3 }}>
                      <Stack gap={0}>
                        <Text size="xs" c="dimmed" tt="uppercase" fw={600}>
                          Booked
                        </Text>
                        <Text fw={700}>{fmtInt(channel.purchased)}</Text>
                      </Stack>
                      <Stack gap={0}>
                        <Text size="xs" c="dimmed" tt="uppercase" fw={600}>
                          Stopped at hold
                        </Text>
                        <Text fw={700}>{fmtInt(channel.stoppedAtHold)}</Text>
                      </Stack>
                      <Stack gap={0}>
                        <Text size="xs" c="dimmed" tt="uppercase" fw={600}>
                          Never left the cart
                        </Text>
                        <Text fw={700}>{fmtInt(channel.stoppedAtCart)}</Text>
                      </Stack>
                    </SimpleGrid>

                    {mediums.length > 0 && (
                      <Stack gap={4} pt="xs">
                        <Text size="xs" c="dimmed" tt="uppercase" fw={600}>
                          By medium
                        </Text>
                        {mediums.map((m) => (
                          <Group key={m.key} justify="space-between" gap="xs">
                            <Text size="sm">{m.key}</Text>
                            <Text size="sm" c="dimmed">
                              {fmtInt(m.purchased)} booked of {fmtInt(m.journeys)} ·{" "}
                              {m.conversionPct}%
                            </Text>
                          </Group>
                        ))}
                      </Stack>
                    )}
                  </Stack>
                </Card>
              </PageSection>

              <PageSection>
                <JourneyTable
                  data={journeys}
                  loading={journeysLoading}
                  outcome={journeyOutcome}
                  onOutcomeChange={setJourneyOutcome}
                  page={journeyPage}
                  pageSize={journeyPageSize}
                  onPageChange={setJourneyPage}
                  onPageSizeChange={(size) => {
                    setJourneyPageSize(size);
                    setJourneyPage(1);
                  }}
                  searchSuffix={searchParams.toString() ? `?${searchParams}` : ""}
                />
              </PageSection>

              <PageSection>
                <Stack gap="sm">
                  <Group justify="space-between" align="center" wrap="wrap">
                    <Title order={5}>
                      Members from this campaign ({fmtInt(members?.total ?? 0)})
                    </Title>
                    <TextInput
                      placeholder="Search by membership number"
                      leftSection={<IconSearch size={16} />}
                      w={280}
                      value={search}
                      onChange={(e) => setSearch(e.currentTarget.value)}
                    />
                  </Group>

                  {loading && !members ? (
                    <Center py="xl">
                      <Loader size="sm" />
                    </Center>
                  ) : (
                    <>
                      <Card className={styles.tableCard} p={0}>
                        <Table.ScrollContainer minWidth={960}>
                          <Table highlightOnHover verticalSpacing="sm" horizontalSpacing="md">
                            <Table.Thead className={styles.tableHead}>
                              <Table.Tr>
                                <Table.Th>Member</Table.Th>
                                <Table.Th>Account</Table.Th>
                                {(summary.availableEventTypes ?? []).map((t) => (
                                  <Table.Th key={t} ta="right">
                                    {eventTypeLabel(t)}
                                  </Table.Th>
                                ))}
                                <Table.Th ta="right">Points</Table.Th>
                                <Table.Th ta="right">Revenue</Table.Th>
                                <Table.Th>First seen</Table.Th>
                              </Table.Tr>
                            </Table.Thead>
                            <Table.Tbody>
                              {rows.map((row) => (
                                <Table.Tr key={row.memberId}>
                                  <Table.Td>
                                    <Stack gap={0}>
                                      <Anchor
                                        component={Link}
                                        to={`/admin/event-analytics/members/${encodeURIComponent(row.memberId)}${
                                          searchParams.toString() ? `?${searchParams}` : ""
                                        }`}
                                        size="sm"
                                        fw={600}
                                        lineClamp={1}
                                      >
                                        {memberDisplayName(row)}
                                      </Anchor>
                                      <Text size="xs" c="dimmed">
                                        {row.memberId}
                                      </Text>
                                    </Stack>
                                  </Table.Td>
                                  <Table.Td>
                                    <Text size="xs" c="dimmed" lineClamp={1}>
                                      {[row.profile?.accountType, row.profile?.country]
                                        .filter(Boolean)
                                        .join(" · ") || "—"}
                                    </Text>
                                  </Table.Td>
                                  {(summary.availableEventTypes ?? []).map((t) => {
                                    const count = row.eventCounts[t] ?? 0;
                                    return (
                                      <Table.Td key={t} ta="right" className={styles.num}>
                                        <Text size="sm" c={count ? undefined : "dimmed"}>
                                          {count}
                                        </Text>
                                      </Table.Td>
                                    );
                                  })}
                                  <Table.Td ta="right" className={styles.num}>
                                    {fmtInt(row.points)}
                                  </Table.Td>
                                  <Table.Td ta="right">
                                    {formatRevenue(row.revenueByCurrency)}
                                  </Table.Td>
                                  <Table.Td>{fmtDate(row.firstSeen)}</Table.Td>
                                </Table.Tr>
                              ))}
                              {rows.length === 0 && (
                                <Table.Tr>
                                  <Table.Td
                                    colSpan={5 + (summary.availableEventTypes?.length ?? 0)}
                                  >
                                    <Text c="dimmed" ta="center" py="md">
                                      No members match that search.
                                    </Text>
                                  </Table.Td>
                                </Table.Tr>
                              )}
                            </Table.Tbody>
                          </Table>
                        </Table.ScrollContainer>
                      </Card>

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