import { useEffect, useMemo, useRef, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { Search, X } from "lucide-react";
import { Input } from "@/components/ui/input";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { StatusBadge } from "@/components/StatusBadge";
import { NewClaimDialog } from "@/components/NewClaimDialog";
import { ClaimDrawer } from "@/components/ClaimDrawer";
import { KeyboardCheatsheet } from "@/components/KeyboardCheatsheet";
import { Skeleton } from "@/components/ui/skeleton";
import { EmptyState } from "@/components/ui/empty-state";
import { ErrorState } from "@/components/ui/error-state";
import { FilterChips, type FilterChipOption } from "@/components/ui/filter-chips";
import { Pagination } from "@/components/ui/pagination";
import { PageHeader } from "@/components/PageHeader";
import { useClaims } from "@/hooks/useClaims";
import { useDrawerUrlState } from "@/hooks/useDrawerUrlState";
import { DrillableCell } from "@/components/drill/DrillableCell";
import { useTailStream } from "@/hooks/useTailStream";
import { useMergedTail } from "@/hooks/useMergedTail";
import { TailStatusPill } from "@/components/TailStatusPill";
import { useAppStore } from "@/store";
import { fmt } from "@/lib/format";
import type { Claim, ClaimStatus } from "@/types";
import { cn } from "@/lib/utils";

const ALL = "all" as const;
const PAGE_SIZE = 25;

const STATUS_OPTIONS: FilterChipOption[] = [
  { value: "all", label: "All" },
  { value: "submitted", label: "Submitted" },
  { value: "accepted", label: "Accepted" },
  { value: "denied", label: "Denied" },
  { value: "paid", label: "Paid" },
  { value: "pending", label: "Pending" },
];

// Whitelist of valid `?status=` values — anything else falls back to ALL.
// Used both to validate the URL param on read AND to guard the Dropdown
// onValueChange so the URL never carries a bogus value.
const VALID_STATUSES: ReadonlySet<ClaimStatus> = new Set<ClaimStatus>([
  "draft",
  "submitted",
  "accepted",
  "denied",
  "paid",
  "pending",
]);

export function Claims() {
  // -------------------------------------------------------------------------
  // URL-driven filter state.
  //
  // The page reads `?status=` and `?sort=` off the URL (via React Router's
  // useSearchParams) so deep links from the Dashboard KPI tiles — e.g.
  // navigate("/claims?status=denied") — actually filter, not just land on
  // a default view. Status and sort/order derive from the URL; pagination
  // (`?page=`) and search (`?q=`) stay local because they're ephemeral
  // view state that doesn't deserve a URL slot.
  //
  // Sort syntax: `?sort=billedAmount` → asc, `?sort=-billedAmount` → desc.
  // The leading `-` is the conventional "negate" prefix used by most
  // list APIs (incl. our own /api/claims), so we mirror it instead of
  // using a separate `?order=` param.
  // -------------------------------------------------------------------------
  const [searchParams, setSearchParams] = useSearchParams();
  const navigate = useNavigate();

  const rawStatus = searchParams.get("status");
  const status: ClaimStatus | typeof ALL =
    rawStatus && VALID_STATUSES.has(rawStatus as ClaimStatus)
      ? (rawStatus as ClaimStatus)
      : ALL;

  const rawSort = searchParams.get("sort");
  let sort: string;
  let order: "asc" | "desc";
  if (rawSort && rawSort.length > 1 && rawSort.startsWith("-")) {
    // Strip the `-` desc-marker, but only when there's an actual field
    // after it. `?sort=-` (prefix only, no field) falls through to the
    // default below — otherwise `slice(1)` would yield `""` and we'd
    // hit the API with `sort=""`.
    sort = rawSort.slice(1);
    order = "desc";
  } else if (rawSort && rawSort !== "-") {
    sort = rawSort;
    order = "asc";
  } else {
    // Default — match the previous hardcoded behavior so existing
    // bookmarks and the "no params" path keep sorting by billed desc.
    // Also catches `?sort=-` (no field after the prefix) and any other
    // degenerate value.
    sort = "billedAmount";
    order = "desc";
  }

  const [npi, setNpi] = useState<string>(ALL);
  const [query, setQuery] = useState("");
  const [page, setPage] = useState(1);
  const [helpOpen, setHelpOpen] = useState(false);
  const searchRef = useRef<HTMLInputElement>(null);

  // Reset pagination whenever the URL-driven filters change. Without
  // this, a deep link like `/claims?status=denied` lands on whatever
  // page the user was last on (e.g. page 5 of an unfiltered list) and
  // shows an empty view. Safe to run on every render of `status` or
  // `sort` because `setPage(1)` is idempotent when already at 1, and
  // changing `page` doesn't trigger this effect.
  useEffect(() => {
    setPage(1);
  }, [status, sort]);

  const { claimId, open, close, setClaimId } = useDrawerUrlState();

  const providers = useAppStore((s) => s.providers);
  const providerMap = useMemo(
    () => Object.fromEntries(providers.map((p) => [p.npi, p])),
    [providers]
  );

  // Drop the `status` query param entirely when going back to "all", so
  // the URL stays clean for sharing. Caller still owns `setPage(1)` to
  // reset pagination.
  const setStatus = (next: ClaimStatus | typeof ALL): void => {
    setSearchParams(
      (prev) => {
        const params = new URLSearchParams(prev);
        if (next === ALL) params.delete("status");
        else params.set("status", next);
        return params;
      },
      { replace: false },
    );
  };

  const params = {
    status: status === ALL ? undefined : status,
    provider_npi: npi === ALL ? undefined : npi,
    sort,
    order,
    limit: PAGE_SIZE,
    offset: (page - 1) * PAGE_SIZE,
  };

  const { data, isLoading, isError, error, refetch, dataUpdatedAt } = useClaims(params);

  const tailFilterFn = useMemo(
    () => (c: Claim) => {
      if (status !== ALL && c.status !== status) return false;
      if (npi !== ALL && c.providerNpi !== npi) return false;
      return true;
    },
    [status, npi],
  );

  const { status: tailStatus, lastEventAt: tailLastEventAt, forceReconnect } =
    useTailStream("claims");
  const items = useMergedTail("claims", data?.items ?? [], tailFilterFn);

  const totals = useMemo(
    () => ({
      billed: items.reduce((s, c) => s + c.billedAmount, 0),
      received: items.reduce((s, c) => s + c.receivedAmount, 0),
    }),
    [items]
  );

  const visible = useMemo(() => {
    const q = query.trim().toLowerCase();
    if (!q) return items;
    return items.filter(
      (c) =>
        c.id.toLowerCase().includes(q) ||
        c.patientName.toLowerCase().includes(q) ||
        c.payerName.toLowerCase().includes(q) ||
        c.cptCode.toLowerCase().includes(q)
    );
  }, [items, query]);

  const filtered = status !== ALL || npi !== ALL;
  const dimBackground = claimId !== null;

  return (
    <>
      <ClaimDrawer
        claimId={claimId}
        claims={items.map((c) => ({ id: c.id }))}
        onClose={close}
        onNavigate={setClaimId}
        onToggleHelp={() => setHelpOpen((v) => !v)}
      />
      <KeyboardCheatsheet
        open={helpOpen}
        onClose={() => setHelpOpen(false)}
      />
      <div
        data-testid="claims-page-body"
        className={cn(
          "space-y-6 lg:space-y-8 animate-fade-in transition-opacity",
          dimBackground && "pointer-events-none opacity-60"
        )}
      >
        <PageHeader
          eyebrow="Claims"
          title={<>All <span className="display italic text-muted-foreground">claims</span></>}
          subtitle="Every claim submitted, accepted, denied, or paid — filtered, sorted, and live."
          actions={
            <TailStatusPill
              status={tailStatus}
              lastEventAt={tailLastEventAt}
              onReconnect={forceReconnect}
            />
          }
          status={
            <NewClaimDialog />
          }
        />

        {isError ? (
          <ErrorState
            message="Couldn't load claims from the backend."
            detail={error instanceof Error ? error.message : String(error)}
            onRetry={() => refetch()}
          />
        ) : null}

        <div className="surface rounded-xl p-4">
          <div className="flex flex-wrap items-center gap-3">
            <div className="relative flex-1 min-w-[240px]">
              <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
              <Input
                ref={searchRef}
                placeholder="Search by claim ID, patient, payer, CPT…"
                value={query}
                onChange={(e) => setQuery(e.target.value)}
                className="pl-9 pr-10"
              />
              {query ? (
                <button
                  type="button"
                  onClick={() => setQuery("")}
                  className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground p-1.5 rounded-md transition-colors hover:bg-muted/60"
                  aria-label="Clear search"
                >
                  <X className="h-3.5 w-3.5" />
                </button>
              ) : null}
            </div>

            <Select
              value={npi}
              onValueChange={(v) => {
                setNpi(v);
                setPage(1);
              }}
            >
              <SelectTrigger className="w-[240px]">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value={ALL}>All providers</SelectItem>
                {providers.map((p) => (
                  <SelectItem key={p.npi} value={p.npi}>
                    {p.npi} — {p.name}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>

          <div className="mt-3.5">
            <FilterChips
              options={STATUS_OPTIONS}
              value={status === ALL ? null : status}
              onChange={(v) => {
                setStatus((v as ClaimStatus | null) ?? ALL);
                setPage(1);
              }}
              eyebrow="Status"
            />
          </div>

          <div className="flex flex-wrap items-center gap-x-6 gap-y-2 mt-4 pt-4 border-t border-border/30 mono text-[11px] text-muted-foreground">
            <span>
              <span className="text-foreground font-medium">
                {fmt.num(data?.total ?? 0)}
              </span>{" "}
              claims
            </span>
            <span>
              Billed{" "}
              <span className="text-foreground font-medium">
                {fmt.usd(totals.billed)}
              </span>
            </span>
            <span>
              Received{" "}
              <span className="text-foreground font-medium">
                {fmt.usd(totals.received)}
              </span>
            </span>
            <span className="ml-auto text-[10.5px] uppercase tracking-[0.14em] text-muted-foreground/70">
              Press <span className="kbd mx-1">?</span> for shortcuts
            </span>
          </div>
        </div>

        <div className="surface rounded-xl overflow-hidden">
          {isLoading ? (
            <div className="p-4 space-y-2">
              {Array.from({ length: 5 }).map((_, i) => (
                <Skeleton key={i} variant="row" />
              ))}
            </div>
          ) : items.length === 0 ? (
            <EmptyState
              eyebrow="Claims · inbox idle"
              message={
                filtered
                  ? "No claims match the current filters."
                  : "Drop an 837P file on the Upload page to populate this list."
              }
            />
          ) : (
            <>
              <Table>
                <TableHeader>
                  <TableRow>
                    <TableHead>Claim</TableHead>
                    <TableHead>Patient</TableHead>
                    <TableHead>Provider</TableHead>
                    <TableHead>Payer</TableHead>
                    <TableHead className="text-right">Billed</TableHead>
                    <TableHead className="text-right">Received</TableHead>
                    <TableHead>Status</TableHead>
                    <TableHead>Submitted</TableHead>
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {visible.map((c) => {
                    const provider = providerMap[c.providerNpi];
                    return (
                      <TableRow
                        key={`${c.id}-${dataUpdatedAt}`}
                        onClick={() => open(c.id)}
                        className={cn("animate-row-flash cursor-pointer")}
                      >
                        <TableCell>
                          <div className="display mono text-[12.5px] text-foreground">{c.id}</div>
                          <div className="mono text-[10.5px] text-muted-foreground">
                            CPT {c.cptCode}
                          </div>
                        </TableCell>
                        <TableCell className="font-medium text-[13px]">{c.patientName}</TableCell>
                        <TableCell>
                          <DrillableCell
                            ariaLabel={`View provider ${provider?.name ?? c.providerNpi}`}
                            onClick={() =>
                              navigate(
                                `/providers?provider=${encodeURIComponent(c.providerNpi)}`,
                              )
                            }
                          >
                            {/*
                              DrillableCell renders an inline-flex button,
                              so the two stacked lines would otherwise land
                              side-by-side. The flex-col wrapper preserves
                              the original "name on top, NPI below" layout.
                            */}
                            <div className="flex flex-col items-start">
                              <div className="text-[13px]">{provider?.name ?? "Unknown"}</div>
                              <div className="mono text-[10.5px] text-muted-foreground">
                                {c.providerNpi}
                              </div>
                            </div>
                          </DrillableCell>
                        </TableCell>
                        <TableCell className="text-muted-foreground text-[13px]">
                          {c.payerName}
                        </TableCell>
                        <TableCell className="text-right display mono">
                          {fmt.usd(c.billedAmount)}
                        </TableCell>
                        <TableCell className="text-right display mono text-muted-foreground">
                          {c.receivedAmount > 0 ? fmt.usd(c.receivedAmount) : "—"}
                        </TableCell>
                        <TableCell>
                          <StatusBadge status={c.status} />
                        </TableCell>
                        <TableCell className="text-muted-foreground mono text-[12px]">
                          {fmt.dateShort(c.submissionDate)}
                        </TableCell>
                      </TableRow>
                    );
                  })}
                </TableBody>
              </Table>
              <div className="px-4 pb-4">
                <Pagination
                  page={page}
                  pageSize={PAGE_SIZE}
                  total={data?.total ?? 0}
                  onPageChange={setPage}
                />
              </div>
            </>
          )}
        </div>
      </div>
    </>
  );
}
