"use client";

import { adminMutationJson } from "@/app/customer/_lib/admin/admin-client-fetch";
import { useMemo, useState } from "react";
import { useRouter } from "next/navigation";
import { Pencil, Plus, Trash2 } from "lucide-react";
import { toast } from "sonner";

import type {
  AccessGroupRow,
  PortalAppCatalogGroup,
  RouteCatalogGroup,
} from "@/app/customer/_lib/admin/access-types";
import { customerCsrfHeader } from "@/lib/customer-csrf.client";
import {
  actionButtonsForRow,
  activeTransactionsExtrasSet,
  applyDepositMergeRules,
  applyRolesUiHiddenRouteRules,
  applyStructureLegacyAliasRules,
  applyFxSwapsMergeRules,
  applyBondAccumulatorMergeRules,
  applyCommodityDerivMergeRules,
  applyStockDerivMergeRules,
  applyTransactionsExtrasRules,
  buildModuleRows,
  filterAppsByAudience,
  filterTransactionsRoutesForCorporateLayout,
  INDIVIDUAL_TRANSACTIONS_EXTRA_MODULES,
  isRouteAllowedForCorporateTransactionsLayout,
  moduleRowDuplicatesAppLabel,
  partitionPermissionItems,
  rolesUiRulesFromApps,
  type ModulePermissionRow,
} from "@/lib/portal-permission-catalog";
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 { Switch } from "@/components/ui/switch";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
} from "@/components/ui/sheet";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";
import { adminPaths } from "@/app/customer/_lib/admin/admin-paths";

type GroupsPageClientProps = {
  tenant: string;
  initialGroups: AccessGroupRow[];
  appCatalog: PortalAppCatalogGroup[];
  initialErrorMessage: string | null;
  canManageRoles?: boolean;
};

type GroupFormState = {
  name: string;
  routes: string[];
};

/** Apply every Transactions-section route-selection rule (extras mutual exclusion + the
 * merged Deposit row's View/Delete mirroring + Structure Form/Edit → Create/Update +
 * drop Roles-UI-hidden sticky grants). Catalog-driven rules are built inside the client. */
export function GroupsPageClient({
  tenant,
  initialGroups,
  appCatalog,
  initialErrorMessage,
  canManageRoles = false,
}: GroupsPageClientProps) {
  const paths = adminPaths(tenant);
  const router = useRouter();
  const [groups, setGroups] = useState(initialGroups);
  const [sheetOpen, setSheetOpen] = useState(false);
  const [editingGroup, setEditingGroup] = useState<AccessGroupRow | null>(null);
  const [form, setForm] = useState<GroupFormState>({ name: "", routes: [] });
  const [saving, setSaving] = useState(false);
  /** Transactions card only — Corporate layout matches the corporate Fx Currency sidebar. */
  const [transactionsCorporate, setTransactionsCorporate] = useState(false);

  const allRoutes = useMemo(
    () => appCatalog.flatMap((app) => app.groups.flatMap((group) => group.routes.map((route) => route.route))),
    [appCatalog],
  );

  const customerAppCatalog = useMemo(
    () => filterAppsByAudience(appCatalog, false),
    [appCatalog],
  );
  const adminAppCatalog = useMemo(
    () => filterAppsByAudience(appCatalog, true),
    [appCatalog],
  );

  const rolesUiRules = useMemo(() => rolesUiRulesFromApps(appCatalog), [appCatalog]);

  function normalizeRoutes(
    routes: string[],
    previousRoutes: string[] = [],
    options?: { mergeDepositAndFxCompanions?: boolean },
  ) {
    const catalog = new Set(allRoutes);
    const catalogOnly = routes.filter((route) => catalog.has(route));
    // Customer layout merges Fixed+Call Deposit and FX+Swaps View/Delete into one checkbox
    // each — mirror rules keep the hidden companion route in sync. Corporate layout shows
    // them as separate rows and must assign independently. Pass mergeDepositAndFxCompanions
    // explicitly when flipping the Corporate toggle — setState is async and the closure
    // would otherwise still see the old layout and re-mirror. (2026-09-23)
    const mergeCompanions = options?.mergeDepositAndFxCompanions ?? !transactionsCorporate;
    const withMergedCompanions = mergeCompanions
      ? applyFxSwapsMergeRules(
          applyDepositMergeRules(
            applyStructureLegacyAliasRules(
              applyRolesUiHiddenRouteRules(catalogOnly, rolesUiRules),
              rolesUiRules,
            ),
          ),
        )
      : applyStructureLegacyAliasRules(
          applyRolesUiHiddenRouteRules(catalogOnly, rolesUiRules),
          rolesUiRules,
        );
    return applyTransactionsExtrasRules(
      applyStockDerivMergeRules(
        applyCommodityDerivMergeRules(applyBondAccumulatorMergeRules(withMergedCompanions)),
      ),
      previousRoutes,
    );
  }

  function toggleAppRoutes(app: PortalAppCatalogGroup, checked: boolean) {
    const routes = app.groups.flatMap((group) => group.routes.map((route) => route.route));
    setForm((current) => ({
      ...current,
      routes: normalizeRoutes(
        checked
          ? [...new Set([...current.routes, ...routes])]
          : current.routes.filter((route) => !routes.includes(route)),
        current.routes,
      ),
    }));
  }

  function isAppFullySelected(app: PortalAppCatalogGroup) {
    const routes = app.groups.flatMap((group) => group.routes.map((route) => route.route));
    return routes.length > 0 && routes.every((route) => form.routes.includes(route));
  }

  function openCreate() {
    setEditingGroup(null);
    setTransactionsCorporate(false);
    // Default to granting everything except the legacy Cash Derivative hub (hidden Roles
    // module). Currency Conversion / Cash Withdrawal stay in the default grant set.
    const defaultRoutes = allRoutes.filter(
      (route) => !INDIVIDUAL_TRANSACTIONS_EXTRA_MODULES.includes(route.split("/", 2)[0]),
    );
    setForm({ name: "", routes: normalizeRoutes(defaultRoutes) });
    setSheetOpen(true);
  }

  async function openEdit(group: AccessGroupRow) {
    setEditingGroup(group);
    setSaving(true);
    try {
      const response = await fetch(paths.groupDetail(group.id), {
        credentials: "same-origin",
        headers: { Accept: "application/json" },
      });
      const data = (await response.json().catch(() => null)) as {
        data?: { routes?: string[]; name?: string };
      } | null;
      const routes = normalizeRoutes(data?.data?.routes ?? []);
      setForm({
        name: data?.data?.name ?? group.name,
        routes,
      });
      setTransactionsCorporate(activeTransactionsExtrasSet(routes) === "corporate");
      setSheetOpen(true);
    } catch {
      toast.error("Could not load role details.");
    } finally {
      setSaving(false);
    }
  }

  function toggleRoute(route: string, checked: boolean) {
    setForm((current) => ({
      ...current,
      routes: normalizeRoutes(
        checked
          ? [...new Set([...current.routes, route])]
          : current.routes.filter((value) => value !== route),
        current.routes,
      ),
    }));
  }

  function toggleModuleRow(row: ModulePermissionRow, checked: boolean) {
    const routes = actionButtonsForRow(row).map((item) => item.route.route);
    setForm((current) => ({
      ...current,
      routes: normalizeRoutes(
        checked
          ? [...new Set([...current.routes, ...routes])]
          : current.routes.filter((route) => !routes.includes(route)),
        current.routes,
      ),
    }));
  }

  function isModuleFullySelected(row: ModulePermissionRow) {
    const routes = actionButtonsForRow(row).map((item) => item.route.route);
    return routes.length > 0 && routes.every((route) => form.routes.includes(route));
  }

  function toggleNavGroupRows(rows: ModulePermissionRow[], checked: boolean) {
    const routes = rows.flatMap((row) => actionButtonsForRow(row).map((item) => item.route.route));
    setForm((current) => ({
      ...current,
      routes: normalizeRoutes(
        checked
          ? [...new Set([...current.routes, ...routes])]
          : current.routes.filter((route) => !routes.includes(route)),
        current.routes,
      ),
    }));
  }

  function isNavGroupFullySelected(rows: ModulePermissionRow[]) {
    const routes = rows.flatMap((row) => actionButtonsForRow(row).map((item) => item.route.route));
    return routes.length > 0 && routes.every((route) => form.routes.includes(route));
  }

  function renderModuleRow(row: ModulePermissionRow, options?: { hideTitle?: boolean }) {
    const actions = actionButtonsForRow(row);
    const hasActions = actions.some((action) =>
      ["view", "create", "update", "delete"].includes(action.key),
    );
    const hideTitle = options?.hideTitle === true;

    if (!hasActions || actions.length <= 1) {
      return actions.map((action) => {
        const checked = form.routes.includes(action.route.route);
        const label = hideTitle ? action.label : row.label;
        const description = hideTitle
          ? action.route.description || row.description
          : row.description || action.route.description;
        return (
          <label key={action.route.route} className="flex items-start gap-2 text-sm">
            <Checkbox
              checked={checked}
              onCheckedChange={(value) => toggleRoute(action.route.route, value === true)}
            />
            <span>
              <span className="font-medium">{label}</span>
              {description ? (
                <span className="block text-xs text-muted-foreground">{description}</span>
              ) : null}
            </span>
          </label>
        );
      });
    }

    if (hideTitle) {
      return (
        <div key={row.key} className="flex flex-wrap gap-3">
          {actions.map((action) => {
            const checked = form.routes.includes(action.route.route);
            return (
              <label
                key={action.route.route}
                className="inline-flex items-center gap-1.5 text-xs font-medium"
              >
                <Checkbox
                  checked={checked}
                  onCheckedChange={(value) => toggleRoute(action.route.route, value === true)}
                />
                {action.label}
              </label>
            );
          })}
        </div>
      );
    }

    return (
      <div key={row.key} className="space-y-2 rounded-md border bg-card px-3 py-2.5">
        <label className="flex items-start gap-2 text-sm">
          <Checkbox
            checked={isModuleFullySelected(row)}
            onCheckedChange={(value) => toggleModuleRow(row, value === true)}
          />
          <span>
            <span className="font-medium">{row.label}</span>
            {row.description ? (
              <span className="block text-xs text-muted-foreground">{row.description}</span>
            ) : null}
          </span>
        </label>
        <div className="flex flex-wrap gap-3 pl-6">
          {actions.map((action) => {
            const checked = form.routes.includes(action.route.route);
            return (
              <label
                key={action.route.route}
                className="inline-flex items-center gap-1.5 text-xs font-medium"
              >
                <Checkbox
                  checked={checked}
                  onCheckedChange={(value) => toggleRoute(action.route.route, value === true)}
                />
                {action.label}
              </label>
            );
          })}
        </div>
      </div>
    );
  }

  function renderAppCard(app: PortalAppCatalogGroup) {
    const isTransactions = app.app === "transactions";
    const appRoutes = app.groups.flatMap((group) => group.routes);
    const displayRoutes = isTransactions
      ? filterTransactionsRoutesForCorporateLayout(appRoutes, transactionsCorporate)
      : appRoutes;
    const displayApp: PortalAppCatalogGroup = isTransactions
      ? {
          ...app,
          groups: [{ label: "Permissions", routes: displayRoutes }],
        }
      : app;
    const mergedSection: RouteCatalogGroup = {
      label: "Permissions",
      // Always pass the full entitled catalog for the active layout — do not filter by
      // selection. Currency Conversion / Cash Withdrawal must remain visible after uncheck.
      routes: displayRoutes,
    };
    // Customer layout: merge Fixed+Call into Deposit and FX+Swaps.
    // Corporate layout: separate modules nested under Fx Currency (sidebar parity).
    const moduleRows = buildModuleRows(
      mergedSection,
      true,
      isTransactions ? !transactionsCorporate : true,
      isTransactions && transactionsCorporate
        ? { nestFxCurrency: true, mergeFxSwaps: false }
        : undefined,
    );
    const permissionItems = partitionPermissionItems(moduleRows);
    const hasGranular = moduleRows.some((row) => row.create || row.update || row.delete);

    return (
      <div key={app.app} className="space-y-3 rounded-xl border bg-card p-4">
        <div className="flex flex-wrap items-start justify-between gap-3">
          <label className="flex min-w-0 flex-1 items-start gap-2 text-sm font-medium">
            <Checkbox
              checked={isAppFullySelected(displayApp)}
              onCheckedChange={(value) => toggleAppRoutes(displayApp, value === true)}
            />
            <span>
              {app.label}
              {app.description ? (
                <span className="block text-xs font-normal text-muted-foreground">
                  {app.description}
                </span>
              ) : null}
            </span>
          </label>
          {isTransactions ? (
            <label className="inline-flex shrink-0 items-center gap-2 text-xs font-medium">
              <Switch
                size="sm"
                checked={transactionsCorporate}
                onCheckedChange={(checked) => {
                  setTransactionsCorporate(checked);
                  if (!checked) {
                    return;
                  }
                  // Corporate layout hides the individual-only Cash Derivative hub, drops
                  // modules not offered to corporate at all, and is View only — drop sticky
                  // grants so they cannot re-save while the toggle is on. Scoped to this
                  // app's own routes so Reports/Settings/Admin selections are untouched.
                  // mergeDepositAndFxCompanions: false — do not re-link Fixed↔Call or
                  // FX↔Swaps while enabling Corporate (state update is async).
                  const transactionsRouteKeys = new Set(appRoutes.map((route) => route.route));
                  setForm((current) => ({
                    ...current,
                    routes: normalizeRoutes(
                      current.routes.filter(
                        (route) =>
                          !transactionsRouteKeys.has(route) ||
                          isRouteAllowedForCorporateTransactionsLayout(route),
                      ),
                      current.routes,
                      { mergeDepositAndFxCompanions: false },
                    ),
                  }));
                }}
              />
              Corporate
            </label>
          ) : null}
        </div>

        <div className="ml-0 space-y-3 rounded-lg border bg-muted/20 p-3 sm:ml-6">
          {isTransactions ? (
            <p className="text-xs text-muted-foreground">
              {transactionsCorporate
                ? "Corporate menu — Cash, Fixed/Call Deposit, Leverage, FX Accumulator, and Swaps under Fx Currency."
                : "Standard menu — Deposit and FX Accumulator / Swaps are merged into single rows."}
            </p>
          ) : null}
          <div className={cn("grid gap-3", hasGranular ? "grid-cols-1" : "sm:grid-cols-2")}>
            {permissionItems.map((item) => {
              if (item.type === "nav_group") {
                return (
                  <div
                    key={`nav-group-${item.key}`}
                    className="space-y-2 rounded-md border bg-card px-3 py-2.5"
                  >
                    <label className="flex items-start gap-2 text-sm">
                      <Checkbox
                        checked={isNavGroupFullySelected(item.rows)}
                        onCheckedChange={(value) => toggleNavGroupRows(item.rows, value === true)}
                      />
                      <span>
                        <span className="font-medium">{item.label}</span>
                        <span className="block text-xs text-muted-foreground">
                          Modules nested under this group
                        </span>
                      </span>
                    </label>
                    <div className="space-y-2 pl-6">{item.rows.map((row) => renderModuleRow(row))}</div>
                  </div>
                );
              }

              return renderModuleRow(item.row, {
                hideTitle: moduleRowDuplicatesAppLabel(item.row.label, app.label),
              });
            })}
          </div>
        </div>
      </div>
    );
  }

  async function handleSave() {
    if (!form.name.trim()) {
      toast.error("Role name is required.");
      return;
    }

    setSaving(true);
    try {
      const url = editingGroup
        ? paths.group(editingGroup.id)
        : paths.groupsCreateSubmit();
      const result = await adminMutationJson(
        url,
        {
          method: "POST",
          credentials: "same-origin",
          headers: {
            "Content-Type": "application/json",
            Accept: "application/json",
            ...customerCsrfHeader(tenant),
          },
          body: JSON.stringify({
            name: form.name.trim(),
            routes: form.routes.map((route) => ({ route, access: "allow" })),
          }),
        },
        "Could not save role.",
      );
      if (!result.ok) return;

      toast.success(result.message ?? "Role saved.");
      setSheetOpen(false);
      router.refresh();
    } finally {
      setSaving(false);
    }
  }

  async function handleDelete(group: AccessGroupRow) {
    if (!window.confirm(`Delete role "${group.name}"?`)) {
      return;
    }

    const result = await adminMutationJson(
      paths.group(group.id),
      {
        method: "DELETE",
        credentials: "same-origin",
        headers: {
          Accept: "application/json",
          ...customerCsrfHeader(tenant),
        },
      },
      "Could not delete role.",
    );
    if (!result.ok) return;

    setGroups((current) => current.filter((item) => item.id !== group.id));
    toast.success(result.message ?? "Role deleted.");
    router.refresh();
  }

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

  return (
    <div className="flex flex-col gap-4">
      <div className="flex items-center justify-between gap-3">
        <div>
          <h1 className="text-2xl font-semibold tracking-tight">Roles & Permissions</h1>
          <p className="text-sm text-muted-foreground">
            {canManageRoles
              ? "Grant subsets of what the platform sold this account (the entitlement ceiling) to RMs, sub-users, and customers. Editing a role never changes the corporate owner’s ceiling — that is set only by platform admin."
              : "View roles used when assigning permissions to Customers. Creating or editing roles requires admin manage access."}
          </p>
        </div>
        {canManageRoles ? (
          <Button onClick={openCreate}>
            <Plus />
            New role
          </Button>
        ) : null}
      </div>

      <div className="rounded-xl border">
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead>Role</TableHead>
              <TableHead>Users</TableHead>
              <TableHead>Routes</TableHead>
              <TableHead className="text-right">Actions</TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {groups.length === 0 ? (
              <TableRow>
                <TableCell colSpan={4} className="py-8 text-center text-muted-foreground">
                  No roles yet. Create one to start assigning permissions.
                </TableCell>
              </TableRow>
            ) : (
              groups.map((group) => (
                <TableRow key={group.id}>
                  <TableCell className="font-medium">{group.name}</TableCell>
                  <TableCell>{group.users_count}</TableCell>
                  <TableCell>
                    <Badge variant="secondary">{group.routes_count} routes</Badge>
                  </TableCell>
                  <TableCell className="text-right">
                    {canManageRoles ? (
                      <div className="flex justify-end gap-2">
                        <Button size="sm" variant="outline" onClick={() => void openEdit(group)} disabled={saving}>
                          <Pencil />
                          Edit
                        </Button>
                        <Button size="sm" variant="outline" onClick={() => void handleDelete(group)}>
                          <Trash2 />
                          Delete
                        </Button>
                      </div>
                    ) : (
                      <span className="text-muted-foreground text-xs">View only</span>
                    )}
                  </TableCell>
                </TableRow>
              ))
            )}
          </TableBody>
        </Table>
      </div>

      <Sheet
        open={sheetOpen}
        onOpenChange={(open) => {
          setSheetOpen(open);
          if (!open) {
            setEditingGroup(null);
          }
        }}
      >
        <SheetContent className="flex h-full !w-[min(860px,calc(100vw-1rem))] !max-w-none flex-col gap-0 overflow-hidden bg-muted/30 p-0">
          <SheetHeader className="shrink-0 border-b bg-card px-6 py-5 pr-14">
            <SheetTitle>{editingGroup ? "Edit role" : "Create role"}</SheetTitle>
            <SheetDescription>
              These checkboxes are grants under the sold ceiling — not the corporate
              owner’s own permissions. Unchecking Administration here only affects users
              assigned this role (e.g. All Accounts / RM); the platform ceiling for this
              account stays unchanged. Stock and Stock Funds also have Sale; Options and
              Accumulators sit under Derivatives.
            </SheetDescription>
          </SheetHeader>

          <div className="min-h-0 flex-1 space-y-4 overflow-y-auto px-6 py-6">
            <div className="space-y-2">
              <Label htmlFor="group-name">Role name</Label>
              <Input
                id="group-name"
                value={form.name}
                onChange={(event) => setForm((current) => ({ ...current, name: event.target.value }))}
                placeholder="Portfolio managers"
              />
            </div>

            <Tabs defaultValue="customer">
              <TabsList>
                <TabsTrigger value="customer">Customer routes</TabsTrigger>
                <TabsTrigger value="admin">Corporate admin routes</TabsTrigger>
              </TabsList>
              <TabsContent value="customer" className="space-y-3 pt-3">
                <p className="text-xs text-muted-foreground">
                  Portfolio modules every portal user can be granted — Settings, Transactions,
                  and Reports.
                </p>
                {customerAppCatalog.length === 0 ? (
                  <p className="rounded-lg border border-dashed p-4 text-center text-sm text-muted-foreground">
                    No customer routes are entitled to this account.
                  </p>
                ) : (
                  customerAppCatalog.map((app) => renderAppCard(app))
                )}
              </TabsContent>
              <TabsContent value="admin" className="space-y-3 pt-3">
                <p className="text-xs text-muted-foreground">
                  Sub-customer and sub-user management, plus Order Blotter, Deepdex, and the
                  Discussion Board. Only relevant to corporate accounts.
                </p>
                {adminAppCatalog.length === 0 ? (
                  <p className="rounded-lg border border-dashed p-4 text-center text-sm text-muted-foreground">
                    No corporate admin routes are entitled to this account.
                  </p>
                ) : (
                  adminAppCatalog.map((app) => renderAppCard(app))
                )}
              </TabsContent>
            </Tabs>
          </div>

          <SheetFooter className="shrink-0 border-t bg-card 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 handleSave()} disabled={saving}>
              {saving ? "Saving..." : "Save role"}
            </Button>
          </SheetFooter>
        </SheetContent>
      </Sheet>
    </div>
  );
}
