"use client";

import { adminMutationJson } from "@/app/customer/_lib/admin/admin-client-fetch";
import { toastApiError } from "@/lib/toast-api-error";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { useCallback, useMemo, useState } from "react";
import {
  LogIn,
  Pencil,
  Plus,
  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 { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  AdminListPaginationBar,
  AdminListSearchInput,
} from "@/app/customer/[tenant]/admin/_components/admin-list-shell";
import {
  useAdminListQuery,
  type AdminListPagination,
  type AdminListPushParams,
} from "@/app/customer/[tenant]/admin/_components/use-admin-list-query";
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";
import { adminPaths } from "@/app/customer/_lib/admin/admin-paths";


type CustomerKind = "live" | "mandate";

type CustomersPageClientProps = {
  tenant: string;
  initialCustomers: AccessCustomerRow[];
  groups: Array<{ group_id: number; name: string }>;
  initialErrorMessage: string | null;
  initialQ: string;
  customerKind: CustomerKind;
  /**
   * Manage capability for the ACTIVE tab — Live and Mandate are fully independent grants
   * (see Yii CorporateCapability::MANDATE_CUSTOMERS_MANAGE), so the server recomputes this
   * per `customerKind` rather than sending one flag for both.
   */
  canManageCustomers?: boolean;
  /** Can this actor view the Model Portfolio tab at all (any grant, not just manage). */
  canViewMandateTab?: boolean;
  pagination: AdminListPagination;
};

type RoleSheetMode = "single" | "bulk";

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

export function CustomersPageClient({
  tenant,
  initialCustomers,
  groups,
  initialErrorMessage,
  initialQ,
  customerKind,
  canManageCustomers = true,
  canViewMandateTab = true,
  pagination,
}: CustomersPageClientProps) {
  const paths = adminPaths(tenant);
  const visibleCustomerKindTabs = CUSTOMER_KIND_TABS.filter(
    (tab) => tab.id !== "mandate" || canViewMandateTab,
  );
  const router = useRouter();
  const patchParams = useCallback(
    (params: URLSearchParams, next: AdminListPushParams) => {
      const kind = (typeof next.customerKind === "string" ? next.customerKind as CustomerKind : undefined) ?? customerKind;
      if (kind === "mandate") {
        params.set("customerKind", "mandate");
      } else {
        params.delete("customerKind");
      }
    },
    [customerKind],
  );
  const {
    searchInput,
    setSearchInput,
    isPending,
    pushListParams,
    rangeStart,
    rangeEnd,
  } = useAdminListQuery({ initialQ, pagination, patchParams });

  const [customers, setCustomers] = useState(initialCustomers);
  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);
  }

  // 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([]);
  }

  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],
  );

  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 result = await adminMutationJson<{
          data?: { items?: AccessCustomerRow[]; updated_count?: number };
        }>(
          paths.customersBulkRole(),
          {
            method: "POST",
            credentials: "same-origin",
            headers: {
              "Content-Type": "application/json",
              Accept: "application/json",
              ...customerCsrfHeader(tenant),
            },
            body: JSON.stringify({
              customer_ids: selectedIds,
              group_id: resolvedGroupId,
            }),
          },
          "Could not bulk-assign customer roles.",
        );
        if (!result.ok || !result.data?.data?.items) return;

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

      if (!editingCustomer) {
        return;
      }

      const result = await adminMutationJson<{ data?: AccessCustomerRow }>(
        paths.customersRole(),
        {
          method: "POST",
          credentials: "same-origin",
          headers: {
            "Content-Type": "application/json",
            Accept: "application/json",
            ...customerCsrfHeader(tenant),
          },
          body: JSON.stringify({
            id: editingCustomer.id,
            group_id: resolvedGroupId,
          }),
        },
        "Could not update customer role.",
      );
      if (!result.ok || !result.data?.data) return;

      const updated = result.data.data;
      setCustomers((current) =>
        current.map((row) =>
          row.id === updated.id ? { ...row, ...updated } : 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;
    }

    const result = await adminMutationJson<{
      data?: { launch_url?: string; open_in_new_tab?: boolean };
    }>(
      paths.customersImpersonate(),
      {
        method: "POST",
        credentials: "same-origin",
        headers: {
          "Content-Type": "application/json",
          Accept: "application/json",
          ...customerCsrfHeader(tenant),
        },
        body: JSON.stringify({ customer_uid: customer.customer_uid }),
      },
      "Could not impersonate customer.",
    );
    if (!result.ok) return;

    const launchUrl = result.data?.data?.launch_url;
    if (!launchUrl) {
      toastApiError(
        new Error("Impersonation launch URL was not returned."),
        "Could not impersonate customer.",
      );
      return;
    }

    // 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) {
      toastApiError(
        new Error("Pop-up blocked. Allow pop-ups for this site and try again."),
        "Could not impersonate customer.",
      );
      return;
    }
    try {
      opened.opener = null;
    } catch {
      // Browser refused the assignment; the tab is on our own portal origin regardless.
    }
  }

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

  // Hide the whole Actions column rather than rendering an empty header with no cell
  // content when this viewer can't manage customers and none of the visible rows are
  // impersonable by them (e.g. a Role without "Impersonate sub-users" granted).
  const showActionsColumn =
    canManageCustomers
    || customers.some((customer) => customer.can_impersonate && customer.customer_uid && customer.permissions?.impersonate);

  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">
            {canManageCustomers
              ? "Assign roles to sub-customers, create new customers (with their own database under this parent), and impersonate them when allowed."
              : "Customers assigned to your account. Impersonate an assigned customer when available."}
          </p>
        </div>
        {canManageCustomers ? (
        <div className="flex flex-wrap gap-2">
          <Button asChild>
            <Link href={paths.customersCreate()}>
              <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>
        ) : null}
      </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"
        >
          {visibleCustomerKindTabs.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>

      <AdminListSearchInput
        value={searchInput}
        onChange={setSearchInput}
        placeholder="Search name, email, company, subdomain…"
        aria-label="Search customers"
      />

      {canManageCustomers && 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>
              {canManageCustomers ? (
              <TableHead className="w-10">
                <Checkbox
                  checked={
                    allSelected ? true : someSelected ? "indeterminate" : false
                  }
                  onCheckedChange={(value) => toggleAll(value === true)}
                  aria-label="Select all customers"
                />
              </TableHead>
              ) : null}
              <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>
              {showActionsColumn ? <TableHead className="text-right">Actions</TableHead> : null}
            </TableRow>
          </TableHeader>
          <TableBody>
            {customers.length === 0 ? (
              <TableRow>
                <TableCell
                  colSpan={8 + (canManageCustomers ? 1 : 0) + (showActionsColumn ? 1 : 0)}
                  className="py-8 text-center text-muted-foreground"
                >
                  {initialQ
                    ? "No customers match your search."
                    : customerKind === "mandate"
                      ? "No model portfolio 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}
                  >
                    {canManageCustomers ? (
                    <TableCell>
                      <Checkbox
                        checked={checked}
                        onCheckedChange={(value) =>
                          toggleCustomer(customer.id, value === true)
                        }
                        aria-label={`Select ${customer.name || customer.email}`}
                      />
                    </TableCell>
                    ) : null}
                    <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>
                    {showActionsColumn ? (
                    <TableCell className="text-right">
                      <div className="inline-flex items-center justify-end gap-2">
                        {canManageCustomers ? (
                          <>
                        <Button
                          size="sm"
                          variant="outline"
                          type="button"
                          onClick={async () => {
                            const result = await adminMutationJson(
                              paths.customersEditSelect(),
                              {
                                method: "POST",
                                credentials: "same-origin",
                                headers: {
                                  Accept: "application/json",
                                  "Content-Type": "application/json",
                                  ...customerCsrfHeader(tenant),
                                },
                                body: JSON.stringify({ id: customer.id }),
                              },
                              "Could not open customer for editing.",
                            );
                            if (!result.ok) return;
                            router.push(paths.customersEdit());
                          }}
                        >
                          <SquarePen />
                          Edit
                        </Button>
                        <Button
                          size="sm"
                          variant="outline"
                          type="button"
                          onClick={() => openEditRole(customer)}
                        >
                          <Pencil />
                          Role
                        </Button>
                          </>
                        ) : null}
                        {customer.can_impersonate && customer.customer_uid && customer.permissions?.impersonate ? (
                          <Button
                            size="sm"
                            variant="outline"
                            type="button"
                            onClick={() => void handleImpersonate(customer)}
                          >
                            <LogIn />
                            Impersonate
                          </Button>
                        ) : null}
                      </div>
                    </TableCell>
                    ) : null}
                  </TableRow>
                );
              })
            )}
          </TableBody>
        </Table>
      </div>

      <AdminListPaginationBar
        pagination={pagination}
        isPending={isPending}
        rangeStart={rangeStart}
        rangeEnd={rangeEnd}
        noun="customer"
        pageSizeId="customers-page-size"
        onPush={pushListParams}
      />

      <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>
  );
}
