import { getMembershipAnalytics } from "@/lib/features/members/query";
import type { CoreMembership } from "@/lib/features/members/types";
import {
  Alert,
  Badge,
  Button,
  Group,
  Pagination,
  Stack,
  Text,
  Title,
  Tooltip,
} from "@mantine/core";
import { IconAlertTriangle, IconShieldLock } from "@tabler/icons-react";
import { useDisclosure } from "@mantine/hooks";
import moment from "moment";
import { useEffect, useState } from "react";
import { Link, useNavigate, useSearchParams } from "react-router";
import {
  MemberAnalyticsSection,
  type MemberAnalytics,
} from "./_components/MemberAnalytics";
import {
  MemberDataTable,
  type MemberColumn,
} from "./_components/MemberDataTable";
import {
  ActiveMemberFilterChips,
  countActiveMemberFilters,
  EMPTY_MEMBER_FILTERS,
  MemberFilterDrawer,
  MemberFilterTrigger,
  type MemberFilterValues,
} from "./_components/MemberFilters";
import { accountStatusTone, recordStateTone } from "./_components/statusColors";

interface Option {
  value: string;
  label: string;
}

const MembersClientPage: React.FC<{
  tableProps: { data: CoreMembership[]; totalRows: number };
  filters: MemberFilterValues;
  filterOptions: { accountTypes: Option[]; accountStatuses: Option[] };
  /** Member-module analytics capability; hides the dashboard without it. */
  canViewAnalytics?: boolean;
  /** Super admins only — the access screen is the module's permission surface. */
  canManageAccess?: boolean;
}> = ({
  tableProps,
  filters,
  filterOptions,
  canViewAnalytics = false,
  canManageAccess = false,
}) => {
  const navigate = useNavigate();
  const [searchParams, setSearchParams] = useSearchParams();
  const [drawerOpened, drawer] = useDisclosure(false);
  const [values, setValues] = useState<MemberFilterValues>(filters);
  /*
   * Analytics loads after the table rather than in the loader: the aggregates
   * scan the whole members table, and awaiting them server-side held the page
   * (and therefore the table) hostage. Cached for 15 min in Core2, so repeat
   * visits are immediate.
   */
  const [analytics, setAnalytics] = useState<MemberAnalytics | undefined>();
  /*
   * A failed fetch used to leave `analytics` undefined forever, which the
   * section reads as "still loading" — so an error was indistinguishable from a
   * slow query and the skeletons never went away.
   */
  const [analyticsError, setAnalyticsError] = useState<string | null>(null);
  const [analyticsAttempt, setAnalyticsAttempt] = useState(0);

  // Serialised so the effect refetches when the applied filters change — the
  // charts would otherwise keep describing the unfiltered base.
  const appliedFilters = JSON.stringify(filters);

  useEffect(() => {
    if (!canViewAnalytics) return;
    let cancelled = false;
    setAnalyticsError(null);
    void getMembershipAnalytics({
      searchTerm: filters.searchTerm || undefined,
      accountTypeIds: filters.accountTypeIds,
      accountStatusIds: filters.accountStatusIds,
      isActive: filters.activeOnly
        ? true
        : filters.inactiveOnly
          ? false
          : undefined,
      createdFrom: filters.createdFrom || undefined,
      createdTo: filters.createdTo || undefined,
    })
      .then((response) => {
        if (cancelled) return;
        if (response?.success && response.data) {
          setAnalytics(response.data as MemberAnalytics);
        } else {
          setAnalyticsError(
            response?.message || "Analytics could not be loaded",
          );
        }
      })
      .catch((error: unknown) => {
        if (cancelled) return;
        setAnalyticsError(
          error instanceof Error ? error.message : "Analytics request failed",
        );
      });
    return () => {
      cancelled = true;
    };
  }, [canViewAnalytics, analyticsAttempt, appliedFilters]);

  const page = Number(searchParams.get("page") ?? "1") || 1;
  const pageSize = Number(searchParams.get("pageSize") ?? "50") || 50;
  const totalPages = Math.max(
    1,
    Math.ceil((tableProps.totalRows ?? 0) / pageSize),
  );

  // Filters live in the URL so the loader applies them server-side and the
  // view stays shareable.
  const apply = (next: MemberFilterValues) => {
    setValues(next);
    const params = new URLSearchParams(searchParams);
    const set = (key: string, value: string) =>
      value ? params.set(key, value) : params.delete(key);

    set("search", next.searchTerm);
    set("accountTypeIds", next.accountTypeIds.join(","));
    set("accountStatusIds", next.accountStatusIds.join(","));
    set(
      "isActive",
      next.activeOnly ? "true" : next.inactiveOnly ? "false" : "",
    );
    set("createdFrom", next.createdFrom);
    set("createdTo", next.createdTo);
    params.set("page", "1");
    setSearchParams(params);
  };

  const reset = () => apply(EMPTY_MEMBER_FILTERS);

  const goToPage = (next: number) => {
    const params = new URLSearchParams(searchParams);
    params.set("page", String(next));
    params.set("pageSize", String(pageSize));
    setSearchParams(params);
  };

  return (
    <Stack gap="lg">
      <Group justify="space-between" align="center">
        <Group gap={8} align="baseline">
          <Title order={6}>All Accounts</Title>
        </Group>
        <Group gap="xs">
          {canManageAccess && (
            <Button
              variant="default"
              leftSection={<IconShieldLock size={16} />}
              component={Link}
              to="/admin/members/access"
            >
              Access
            </Button>
          )}
          <MemberFilterTrigger
            activeCount={countActiveMemberFilters(values)}
            onClick={drawer.open}
          />
        </Group>
      </Group>

      {canViewAnalytics &&
        (analyticsError ? (
          <Alert
            color="orange"
            variant="light"
            title="Analytics unavailable"
            icon={<IconAlertTriangle size={16} />}
          >
            <Group justify="space-between" align="center">
              <Text fz="sm">{analyticsError}</Text>
              <Button
                size="compact-xs"
                variant="light"
                onClick={() => setAnalyticsAttempt((count) => count + 1)}
              >
                Retry
              </Button>
            </Group>
          </Alert>
        ) : (
          <MemberAnalyticsSection analytics={analytics} />
        ))}

      <ActiveMemberFilterChips
        values={values}
        options={filterOptions}
        onChange={apply}
        onReset={reset}
      />

      <MemberDataTable
        rows={tableProps.data}
        columns={columns}
        minWidth={1400}
        rowKey={(row) => `${row.membership_number}-${row.member_number}`}
        onRowClick={(row) => void navigate(`${row.membership_number}`)}
        emptyMessage="No accounts found"
      />

      {totalPages > 1 && (
        <Group justify="flex-end">
          <Pagination
            size="sm"
            value={page}
            total={totalPages}
            onChange={goToPage}
          />
        </Group>
      )}

      <MemberFilterDrawer
        opened={drawerOpened}
        onClose={drawer.close}
        values={values}
        options={filterOptions}
        onChange={apply}
        onReset={reset}
      />
    </Stack>
  );
};

export default MembersClientPage;

const columns: MemberColumn<CoreMembership>[] = [
  {
    key: "membership_number",
    header: "Membership Number",
    render: (row) => <Text fw={600}>{row.membership_number}</Text>,
  },
  {
    key: "owner",
    header: "Primary Contact",
    render: (row) => {
      const name = [row.first_name, row.last_name].filter(Boolean).join(" ");
      return (
        <Stack gap={0}>
          <Text fz="sm" fw={500}>
            {name || "NA"}
          </Text>
          <Text fz={11} c="dimmed">
            {row.email || "NA"}
          </Text>
        </Stack>
      );
    },
  },
  {
    key: "member_number",
    header: "Member Number",
    numeric: true,
    render: (row) => row.member_number ?? "NA",
  },
  {
    key: "account_type_name",
    header: "Account Type",
    render: (row) => row.account_type_name ?? "NA",
  },
  {
    key: "account_status",
    header: "Account Status",
    render: (row) => (
      <Badge
        {...accountStatusTone(row.account_status_name)}
        radius={4}
        tt="capitalize"
      >
        {row.account_status_name?.toLowerCase() ?? "NA"}
      </Badge>
    ),
  },
  {
    key: "record_state",
    header: "Record",
    render: (row) => (
      <Group gap={4}>
        <Badge {...recordStateTone(row.is_active)} size="sm" tt="capitalize">
          {row.is_active ? "active" : "inactive"}
        </Badge>
        {row.is_deleted ? (
          <Badge variant="light" color="red" size="sm">
            deleted
          </Badge>
        ) : null}
      </Group>
    ),
  },
  {
    key: "ownership",
    header: "Ownership",
    render: (row) => (
      // App-side flags: Viewpoint is the authority on ownership and these are
      // often unset, so "contact" here means "not flagged", not "not the owner".
      <Tooltip
        label={
          row.is_owner || row.is_family
            ? "From the member profile"
            : "Not flagged in the app profile — Viewpoint holds the authoritative ownership"
        }
      >
        <Text fz="sm" tt="capitalize" c={row.is_owner ? undefined : "dimmed"}>
          {row.is_owner ? "owner" : row.is_family ? "family" : "contact"}
        </Text>
      </Tooltip>
    ),
  },
  {
    key: "created_at",
    header: "Synced At",
    render: (row) => moment(row.created_at).format("LLL"),
  },
];
