"use client";

import { toastApiError } from "@/lib/toast-api-error";
import {
  usePathname,
  useRouter,
  useSearchParams,
} from "next/navigation";
import Link from "next/link";
import { useEffect, useMemo, useState, useTransition } from "react";
import {
  ChevronLeft,
  ChevronRight,
  ChevronsLeft,
  ChevronsRight,
  LogIn,
  Pencil,
  Plus,
  Search,
  SquarePen,
  Users,
} from "lucide-react";
import { toast } from "sonner";

import type { AccessCustomerRow } from "@/app/customer/_lib/admin/access-types";
import { customerCsrfHeader } from "@/lib/customer-csrf.client";
import { ErrorBanner } from "@/components/shared/error-banner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
} from "@/components/ui/sheet";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { cn } from "@/lib/utils";

type PaginationMeta = {
  page: number;
  pageSize: number;
  totalCount: number;
  pageCount: number;
};

type CustomerKind = "live" | "mandate";

type CustomersPageClientProps = {
  tenant: string;
  initialCustomers: AccessCustomerRow[];
  groups: Array<{ group_id: number; name: string }>;
  initialErrorMessage: string | null;
  initialQ: string;
  customerKind: CustomerKind;
  pagination: PaginationMeta;
};

type RoleSheetMode = "single" | "bulk";

const PAGE_SIZES = [10, 20, 30, 50];

const CUSTOMER_KIND_TABS: Array<{ id: CustomerKind; label: string }> = [
  { id: "live", label: "Live customers" },
  { id: "mandate", label: "Mandate customers" },
];

export function CustomersPageClient({
  tenant,
  initialCustomers,
  groups,
  initialErrorMessage,
  initialQ,
  customerKind,
  pagination,
}: CustomersPageClientProps) {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const [isPending, startTransition] = useTransition();

  const [customers, setCustomers] = useState(initialCustomers);
  const [searchInput, setSearchInput] = useState(initialQ);
  const [selectedIds, setSelectedIds] = useState<number[]>([]);
  const [sheetOpen, setSheetOpen] = useState(false);
  const [sheetMode, setSheetMode] = useState<RoleSheetMode>("single");
  const [editingCustomer, setEditingCustomer] = useState<AccessCustomerRow | null>(null);
  const [groupId, setGroupId] = useState("");
  const [saving, setSaving] = useState(false);

  // Re-sync local state when the server sends a new page of data.
  //
  // These compare against the previous prop *during render* rather than in an
  // effect. An effect would commit and paint the stale value first, then set
  // state and render again; comparing during render lets React discard the
  // in-progress output and re-render immediately, so the stale row set is never
  // shown. Local edits (optimistic role/group updates, typing, selection) still
  // win until the server sends something new.
  const [syncedCustomers, setSyncedCustomers] = useState(initialCustomers);
  if (syncedCustomers !== initialCustomers) {
    setSyncedCustomers(initialCustomers);
    setCustomers(initialCustomers);
  }

  const [syncedQ, setSyncedQ] = useState(initialQ);
  if (syncedQ !== initialQ) {
    setSyncedQ(initialQ);
    setSearchInput(initialQ);
  }

  // Selection is per result set, so clear it whenever the page/size/query/tab moves.
  const selectionScope = `${pagination.page}|${pagination.pageSize}|${initialQ}|${customerKind}`;
  const [syncedSelectionScope, setSyncedSelectionScope] = useState(selectionScope);
  if (syncedSelectionScope !== selectionScope) {
    setSyncedSelectionScope(selectionScope);
    setSelectedIds([]);
  }

  useEffect(() => {
    const trimmed = searchInput.trim();
    if (trimmed === initialQ.trim()) {
      return;
    }

    const handle = window.setTimeout(() => {
      const params = new URLSearchParams(searchParams.toString());
      if (trimmed) {
        params.set("q", trimmed);
      } else {
        params.delete("q");
      }
      params.set("page", "1");
      if (!params.get("pageSize")) {
        params.set("pageSize", String(pagination.pageSize));
      }
      startTransition(() => {
        router.push(`${pathname}?${params.toString()}`);
      });
    }, 350);

    return () => window.clearTimeout(handle);
  }, [
    searchInput,
    initialQ,
    pathname,
    router,
    searchParams,
    pagination.pageSize,
  ]);

  function pushListParams(next: {
    page?: number;
    pageSize?: number;
    q?: string;
    customerKind?: CustomerKind;
  }) {
    const params = new URLSearchParams(searchParams.toString());
    const page = next.page ?? pagination.page;
    const pageSize = next.pageSize ?? pagination.pageSize;
    const q = next.q !== undefined ? next.q.trim() : initialQ.trim();
    const kind = next.customerKind ?? customerKind;

    params.set("page", String(page));
    params.set("pageSize", String(pageSize));
    if (q) {
      params.set("q", q);
    } else {
      params.delete("q");
    }
    if (kind === "mandate") {
      params.set("customerKind", "mandate");
    } else {
      params.delete("customerKind");
    }

    startTransition(() => {
      router.push(`${pathname}?${params.toString()}`);
    });
  }

  const allSelected = customers.length > 0 && selectedIds.length === customers.length;
  const someSelected = selectedIds.length > 0 && selectedIds.length < customers.length;

  const selectedCustomers = useMemo(
    () => customers.filter((customer) => selectedIds.includes(customer.id)),
    [customers, selectedIds],
  );

  const rangeStart =
    pagination.totalCount === 0 ? 0 : (pagination.page - 1) * pagination.pageSize + 1;
  const rangeEnd = Math.min(pagination.page * pagination.pageSize, pagination.totalCount);

  function toggleCustomer(id: number, checked: boolean) {
    setSelectedIds((current) =>
      checked ? [...new Set([...current, id])] : current.filter((value) => value !== id),
    );
  }

  function toggleAll(checked: boolean) {
    setSelectedIds(checked ? customers.map((customer) => customer.id) : []);
  }

  function openEditRole(customer: AccessCustomerRow) {
    setSheetMode("single");
    setEditingCustomer(customer);
    setGroupId(customer.group_id ? String(customer.group_id) : "__none__");
    setSheetOpen(true);
  }

  function openBulkAssign() {
    if (selectedIds.length === 0) {
      toast.error("Select at least one customer.");
      return;
    }
    setSheetMode("bulk");
    setEditingCustomer(null);
    setGroupId("__none__");
    setSheetOpen(true);
  }

  async function handleSaveRole() {
    setSaving(true);
    try {
      const resolvedGroupId = groupId === "__none__" || groupId === "" ? null : Number(groupId);

      if (sheetMode === "bulk") {
        const response = await fetch(`/customer/${tenant}/admin/customers/bulk-role`, {
          method: "POST",
          credentials: "same-origin",
          headers: {
          "Content-Type": "application/json",
          Accept: "application/json",
          ...customerCsrfHeader(tenant),
        },
          body: JSON.stringify({
            customer_ids: selectedIds,
            group_id: resolvedGroupId,
          }),
        });
        const payload = (await response.json().catch(() => null)) as
          | {
              status?: string;
              message?: string;
              data?: { items?: AccessCustomerRow[]; updated_count?: number };
            }
          | null;

        if (!response.ok || payload?.status !== "success" || !payload.data?.items) {
          throw new Error(payload?.message ?? "Could not bulk-assign customer roles.");
        }

        const byId = new Map(payload.data.items.map((item) => [item.id, item]));
        setCustomers((current) =>
          current.map((row) => (byId.has(row.id) ? { ...row, ...byId.get(row.id)! } : row)),
        );
        toast.success(payload.message ?? "Roles assigned.");
        setSelectedIds([]);
        setSheetOpen(false);
        router.refresh();
        return;
      }

      if (!editingCustomer) {
        return;
      }

      const response = await fetch(`/customer/${tenant}/admin/customers/role`, {
        method: "POST",
        credentials: "same-origin",
        headers: {
          "Content-Type": "application/json",
          Accept: "application/json",
          ...customerCsrfHeader(tenant),
        },
        body: JSON.stringify({ id: editingCustomer.id, group_id: resolvedGroupId }),
      });
      const payload = (await response.json().catch(() => null)) as
        | { status?: string; message?: string; data?: AccessCustomerRow }
        | null;

      if (!response.ok || payload?.status !== "success" || !payload.data) {
        throw new Error(payload?.message ?? "Could not update customer role.");
      }

      setCustomers((current) =>
        current.map((row) => (row.id === payload.data!.id ? { ...row, ...payload.data! } : row)),
      );
      toast.success("Customer role updated.");
      setSheetOpen(false);
      router.refresh();
    } catch (error) {
      toastApiError(error, "Could not update customer role.");
    } finally {
      setSaving(false);
    }
  }

  async function handleImpersonate(customer: AccessCustomerRow) {
    if (!customer.customer_uid) {
      toast.error("Customer is missing a portal identifier.");
      return;
    }

    try {
      const response = await fetch(`/customer/${tenant}/admin/customers/impersonate`, {
        method: "POST",
        credentials: "same-origin",
        headers: {
          "Content-Type": "application/json",
          Accept: "application/json",
          ...customerCsrfHeader(tenant),
        },
        body: JSON.stringify({ customer_uid: customer.customer_uid }),
      });
      const payload = (await response.json().catch(() => null)) as
        | {
            status?: string;
            message?: string;
            data?: {
              launch_url?: string;
              open_in_new_tab?: boolean;
            };
          }
        | null;

      if (!response.ok || payload?.status !== "success") {
        throw new Error(payload?.message ?? "Could not start impersonation.");
      }

      const launchUrl = payload.data?.launch_url;
      if (!launchUrl) {
        throw new Error("Impersonation launch URL was not returned.");
      }

      // No `noopener` here: it makes `window.open` return null even on success, which turned
      // every launch into a false "pop-up blocked" error. The opener is severed explicitly.
      const opened = window.open(launchUrl, "_blank");
      if (!opened) {
        throw new Error("Pop-up blocked. Allow pop-ups for this site and try again.");
      }
      try {
        opened.opener = null;
      } catch {
        // Browser refused the assignment; the tab is on our own portal origin regardless.
      }
    } catch (error) {
      toastApiError(error, "Could not impersonate customer.");
    }
  }

  if (initialErrorMessage) {
    return <ErrorBanner message={initialErrorMessage} />;
  }

  return (
    <div className={`flex flex-col gap-4${isPending ? " opacity-70" : ""}`}>
      <div className="flex flex-wrap items-start justify-between gap-3">
        <div>
          <h1 className="text-2xl font-semibold tracking-tight">Customers</h1>
          <p className="text-sm text-muted-foreground">
            Assign roles to sub-customers, create new customers (with their own
            database under this parent), and impersonate them when allowed.
          </p>
        </div>
        <div className="flex flex-wrap gap-2">
          <Button asChild>
            <Link href={`/customer/${tenant}/admin/customers/create`}>
              <Plus />
              Create customer
            </Link>
          </Button>
          <Button
            type="button"
            variant="outline"
            onClick={openBulkAssign}
            disabled={selectedIds.length === 0}
          >
            <Users />
            Bulk assign role
            {selectedIds.length > 0 ? ` (${selectedIds.length})` : ""}
          </Button>
        </div>
      </div>

      <nav className="inline-flex" aria-label="Customer kind">
        <ul
          className="flex list-none flex-row items-stretch gap-1 rounded-lg border bg-muted/60 p-1"
          role="tablist"
        >
          {CUSTOMER_KIND_TABS.map((tab) => {
            const isActive = tab.id === customerKind;
            return (
              <li key={tab.id} role="presentation">
                <button
                  type="button"
                  role="tab"
                  aria-selected={isActive}
                  className={cn(
                    "inline-flex items-center justify-center rounded-md px-3.5 py-2 text-sm font-medium whitespace-nowrap transition-colors",
                    isActive
                      ? "bg-background text-foreground shadow-sm"
                      : "text-muted-foreground hover:text-foreground",
                  )}
                  onClick={() => {
                    if (tab.id === customerKind) {
                      return;
                    }
                    pushListParams({ page: 1, customerKind: tab.id });
                  }}
                >
                  {tab.label}
                </button>
              </li>
            );
          })}
        </ul>
      </nav>

      <div className="relative max-w-md">
        <Search className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
        <Input
          value={searchInput}
          onChange={(event) => setSearchInput(event.target.value)}
          placeholder="Search name, email, company, subdomain…"
          className="pl-9"
          aria-label="Search customers"
        />
      </div>

      {selectedIds.length > 0 ? (
        <div className="flex flex-wrap items-center justify-between gap-3 rounded-xl border bg-muted/30 px-4 py-3 text-sm">
          <span>
            <span className="font-medium">{selectedIds.length}</span> customer
            {selectedIds.length === 1 ? "" : "s"} selected
          </span>
          <div className="flex gap-2">
            <Button type="button" variant="outline" size="sm" onClick={() => setSelectedIds([])}>
              Clear
            </Button>
            <Button type="button" size="sm" onClick={openBulkAssign}>
              Assign role
            </Button>
          </div>
        </div>
      ) : null}

      <div className="rounded-xl border">
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead className="w-10">
                <Checkbox
                  checked={allSelected ? true : someSelected ? "indeterminate" : false}
                  onCheckedChange={(value) => toggleAll(value === true)}
                  aria-label="Select all customers"
                />
              </TableHead>
              <TableHead>Name</TableHead>
              <TableHead>Email</TableHead>
              <TableHead>Subdomain</TableHead>
              <TableHead>Type</TableHead>
              <TableHead>RM</TableHead>
              <TableHead>Created by</TableHead>
              <TableHead>Role</TableHead>
              <TableHead>Status</TableHead>
              <TableHead className="text-right">Actions</TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {customers.length === 0 ? (
              <TableRow>
                <TableCell colSpan={10} className="py-8 text-center text-muted-foreground">
                  {initialQ
                    ? "No customers match your search."
                    : customerKind === "mandate"
                      ? "No mandate customers yet."
                      : "No live customers yet."}
                </TableCell>
              </TableRow>
            ) : (
              customers.map((customer) => {
                const checked = selectedIds.includes(customer.id);
                return (
                  <TableRow key={customer.id} data-state={checked ? "selected" : undefined}>
                    <TableCell>
                      <Checkbox
                        checked={checked}
                        onCheckedChange={(value) => toggleCustomer(customer.id, value === true)}
                        aria-label={`Select ${customer.name || customer.email}`}
                      />
                    </TableCell>
                    <TableCell className="font-medium">
                      <div className="flex flex-col gap-0.5">
                        <span>
                          {customer.name || customer.company_name || `Customer #${customer.id}`}
                        </span>
                        {customer.company_name && customer.name ? (
                          <span className="text-xs text-muted-foreground">{customer.company_name}</span>
                        ) : null}
                      </div>
                    </TableCell>
                    <TableCell>{customer.email}</TableCell>
                    <TableCell>
                      {customer.subdomain ?? <span className="text-muted-foreground">—</span>}
                    </TableCell>
                    <TableCell>
                      {customer.account_type_label ?? customer.account_type ?? (
                        <span className="text-muted-foreground">—</span>
                      )}
                    </TableCell>
                    <TableCell>
                      {customer.primary_rm_label ? (
                        <span className="text-sm">{customer.primary_rm_label}</span>
                      ) : (
                        <span className="text-muted-foreground">—</span>
                      )}
                    </TableCell>
                    <TableCell>
                      {customer.created_by_name ? (
                        <span className="text-sm">{customer.created_by_name}</span>
                      ) : (
                        <span className="text-muted-foreground">—</span>
                      )}
                    </TableCell>
                    <TableCell>
                      {customer.group_name ?? <span className="text-muted-foreground">No role</span>}
                    </TableCell>
                    <TableCell>
                      <Badge variant={customer.status === "active" ? "secondary" : "outline"}>
                        {customer.status}
                      </Badge>
                    </TableCell>
                    <TableCell className="text-right">
                      <div className="inline-flex items-center justify-end gap-2">
                        <Button
                          size="sm"
                          variant="outline"
                          type="button"
                          onClick={async () => {
                            try {
                              const response = await fetch(
                                `/customer/${tenant}/admin/customers/edit/select`,
                                {
                                  method: "POST",
                                  credentials: "same-origin",
                                  headers: {
                                    Accept: "application/json",
                                    "Content-Type": "application/json",
                                    ...customerCsrfHeader(tenant),
                                  },
                                  body: JSON.stringify({ id: customer.id }),
                                },
                              );
                              const payload = (await response.json().catch(() => null)) as {
                                status?: string;
                                message?: string;
                              } | null;
                              if (!response.ok || payload?.status !== "success") {
                                throw new Error(
                                  payload?.message ?? "Could not open customer for editing.",
                                );
                              }
                              router.push(`/customer/${tenant}/admin/customers/edit`);
                            } catch (error) {
                              toastApiError(error, "Could not open customer for editing.");
                            }
                          }}
                        >
                          <SquarePen />
                          Edit
                        </Button>
                        <Button
                          size="sm"
                          variant="outline"
                          type="button"
                          onClick={() => openEditRole(customer)}
                        >
                          <Pencil />
                          Role
                        </Button>
                        {customer.can_impersonate && customer.customer_uid ? (
                          <Button
                            size="sm"
                            variant="outline"
                            type="button"
                            onClick={() => void handleImpersonate(customer)}
                          >
                            <LogIn />
                            Impersonate
                          </Button>
                        ) : null}
                      </div>
                    </TableCell>
                  </TableRow>
                );
              })
            )}
          </TableBody>
        </Table>
      </div>

      <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
        <p className="text-sm text-muted-foreground">
          {pagination.totalCount === 0
            ? "0 customers"
            : `Showing ${rangeStart}–${rangeEnd} of ${pagination.totalCount} customer${pagination.totalCount === 1 ? "" : "s"}`}
        </p>
        <div className="flex w-full flex-wrap items-center justify-end gap-4 sm:w-auto">
          <div className="flex items-center gap-2">
            <Label htmlFor="customers-page-size" className="text-sm font-medium whitespace-nowrap">
              Rows per page
            </Label>
            <Select
              value={String(pagination.pageSize)}
              onValueChange={(value) =>
                pushListParams({ page: 1, pageSize: Number(value) })
              }
            >
              <SelectTrigger size="sm" className="w-20" id="customers-page-size">
                <SelectValue />
              </SelectTrigger>
              <SelectContent side="top">
                {PAGE_SIZES.map((size) => (
                  <SelectItem key={size} value={String(size)}>
                    {size}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          <div className="text-sm font-medium tabular-nums">
            Page {pagination.page} of {Math.max(pagination.pageCount, 1)}
          </div>
          <div className="flex items-center gap-1">
            <Button
              variant="outline"
              size="icon"
              className="size-8"
              type="button"
              disabled={pagination.page <= 1 || isPending}
              onClick={() => pushListParams({ page: 1 })}
              aria-label="First page"
            >
              <ChevronsLeft className="size-4" />
            </Button>
            <Button
              variant="outline"
              size="icon"
              className="size-8"
              type="button"
              disabled={pagination.page <= 1 || isPending}
              onClick={() => pushListParams({ page: pagination.page - 1 })}
              aria-label="Previous page"
            >
              <ChevronLeft className="size-4" />
            </Button>
            <Button
              variant="outline"
              size="icon"
              className="size-8"
              type="button"
              disabled={pagination.page >= pagination.pageCount || isPending}
              onClick={() => pushListParams({ page: pagination.page + 1 })}
              aria-label="Next page"
            >
              <ChevronRight className="size-4" />
            </Button>
            <Button
              variant="outline"
              size="icon"
              className="size-8"
              type="button"
              disabled={pagination.page >= pagination.pageCount || isPending}
              onClick={() => pushListParams({ page: pagination.pageCount })}
              aria-label="Last page"
            >
              <ChevronsRight className="size-4" />
            </Button>
          </div>
        </div>
      </div>

      <Sheet
        open={sheetOpen}
        onOpenChange={(open) => {
          setSheetOpen(open);
          if (!open) {
            setEditingCustomer(null);
          }
        }}
      >
        <SheetContent className="flex h-full !w-[min(480px,calc(100vw-1rem))] !max-w-none flex-col gap-0 overflow-hidden p-0">
          <SheetHeader className="shrink-0 border-b px-6 py-5 pr-14">
            <SheetTitle>
              {sheetMode === "bulk" ? "Bulk assign role" : "Assign role"}
            </SheetTitle>
            <SheetDescription>
              {sheetMode === "bulk"
                ? `Apply one role to ${selectedIds.length} selected customer${selectedIds.length === 1 ? "" : "s"}.`
                : editingCustomer
                  ? `Assign a role for ${editingCustomer.name || editingCustomer.email || `#${editingCustomer.id}`}.`
                  : "Assign a role created under Admin → Roles."}
            </SheetDescription>
          </SheetHeader>

          <div className="min-h-0 flex-1 space-y-4 overflow-y-auto px-6 py-6">
            {sheetMode === "bulk" ? (
              <div className="rounded-lg border bg-muted/20 p-3 text-sm">
                <p className="mb-2 font-medium">Selected customers</p>
                <ul className="max-h-40 space-y-1 overflow-y-auto text-muted-foreground">
                  {selectedCustomers.map((customer) => (
                    <li key={customer.id}>
                      {customer.name || customer.company_name || customer.email || `#${customer.id}`}
                    </li>
                  ))}
                </ul>
              </div>
            ) : null}

            <div className="space-y-2">
              <Label htmlFor="customer-role">Role</Label>
              <Select value={groupId || "__none__"} onValueChange={setGroupId}>
                <SelectTrigger id="customer-role">
                  <SelectValue placeholder="Select a role" />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="__none__">No role (view only)</SelectItem>
                  {groups.map((group) => (
                    <SelectItem key={group.group_id} value={String(group.group_id)}>
                      {group.name}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              <p className="text-xs text-muted-foreground">
                Create and edit roles under Admin → Roles. Include Create/Update/Delete per module
                when the customer should mutate holdings.
              </p>
            </div>
          </div>

          <SheetFooter className="shrink-0 border-t px-6 py-4 sm:flex-row sm:justify-end">
            <Button type="button" variant="outline" onClick={() => setSheetOpen(false)} disabled={saving}>
              Cancel
            </Button>
            <Button type="button" onClick={() => void handleSaveRole()} disabled={saving}>
              {saving ? "Saving…" : sheetMode === "bulk" ? "Assign to selected" : "Save role"}
            </Button>
          </SheetFooter>
        </SheetContent>
      </Sheet>
    </div>
  );
}
