"use client";

import * as React from "react";
import {
  ArrowDown,
  ArrowUp,
  ChevronLeft,
  ChevronRight,
  Download,
  FolderCheck,
  FolderPlus,
  Layers,
  type LucideIcon,
  RefreshCw,
  Rows3,
  Save,
  Search,
  Undo2,
  X,
} from "lucide-react";
import { toast } from "sonner";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";
import { formatPrice } from "@/lib/format/numbers";

import {
  createAssetDefinitionGroup,
  loadAssetDefinitionSettings,
  saveAssetDefinitionAssignments,
} from "../_lib/asset-definition-api";
import type {
  AssetDefinitionBridgeData,
  AssetDefinitionData,
  AssetDefinitionGroupSummaryData,
  AssetDefinitionView,
} from "../_lib/asset-definition-server-api";
import { errorMessageOr, toastApiError } from "@/lib/toast-api-error";
import { buildCsv, CSV_MIME, downloadCsvText } from "@/lib/csv-export";

type Filters = {
  macro: string;
  assetClass: string;
  assetType: string;
  groupName: string;
};

const EMPTY_FILTERS: Filters = { macro: "", assetClass: "", assetType: "", groupName: "" };
const NONE_VALUE = "__not_grouped__";

function isDefinitionData(
  data: AssetDefinitionData | AssetDefinitionBridgeData | AssetDefinitionGroupSummaryData | null,
): data is AssetDefinitionData {
  return data !== null && "pagination" in data && "groups" in data;
}

function number(value: number): string {
  return formatPrice(value, { minimumFractionDigits: 0, maximumFractionDigits: 2 });
}

/** Yii1 stores labels like `Fixed Income<small class="tik">Bond</small>`. */
function parseLegacyLabel(value: string | null | undefined): { primary: string; secondary: string } {
  const raw = String(value ?? "");
  const secondaryMatch = raw.match(/<small[^>]*class=["']?tik["']?[^>]*>([\s\S]*?)<\/small>/i);
  const secondary = secondaryMatch ? stripHtml(secondaryMatch[1]).trim() : "";
  const primary = stripHtml(raw.replace(/<small[^>]*class=["']?tik["']?[^>]*>[\s\S]*?<\/small>/gi, " ")).replace(/\s+/g, " ").trim();
  return { primary: primary || "—", secondary };
}

function stripHtml(value: string): string {
  return value
    .replace(/<[^>]*>/g, " ")
    .replace(/&nbsp;/gi, " ")
    .replace(/&amp;/gi, "&")
    .replace(/&lt;/gi, "<")
    .replace(/&gt;/gi, ">")
    .replace(/&quot;/gi, '"')
    .replace(/&#39;/g, "'")
    .replace(/\s+/g, " ")
    .trim();
}

function LegacyLabel({ value, className }: { value: string | null | undefined; className?: string }) {
  const { primary, secondary } = parseLegacyLabel(value);
  return (
    <div className={className}>
      <div className="whitespace-normal">{primary}</div>
      {secondary ? <div className="text-muted-foreground text-xs leading-tight">{secondary}</div> : null}
    </div>
  );
}

function plainLabel(value: string | null | undefined): string {
  const { primary, secondary } = parseLegacyLabel(value);
  return secondary ? `${primary} (${secondary})` : primary;
}

/**
 * Cells carry legacy "primary|secondary" labels, so they are flattened before
 * escaping; the escaping itself is the shared one.
 */
function downloadCsv(filename: string, headers: string[], rows: unknown[][]) {
  const text = buildCsv(headers, rows, (row) => row.map((cell) => plainLabel(String(cell ?? ""))));
  downloadCsvText({ filename, text, mimeType: CSV_MIME });
}

function StatTile({ icon: Icon, label, value }: { icon: LucideIcon; label: string; value: number | string }) {
  return (
    <div className="flex items-center gap-3 rounded-xl border bg-card p-3.5">
      <span className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
        <Icon className="size-4.5" />
      </span>
      <div className="min-w-0">
        <p className="font-semibold text-lg leading-tight tabular-nums">{value}</p>
        <p className="truncate text-muted-foreground text-xs">{label}</p>
      </div>
    </div>
  );
}

function GroupName({ name, fallback }: { name: string | null | undefined; fallback: string }) {
  return name ? (
    <Badge variant="secondary" className="font-normal">
      {name}
    </Badge>
  ) : (
    <span className="text-muted-foreground text-sm">{fallback}</span>
  );
}

const TH = "h-10 bg-muted/40 text-muted-foreground text-xs uppercase tracking-wide";

export function SettingsPanelAssetDefinition({ tenant }: { tenant: string }) {
  const [view, setView] = React.useState<AssetDefinitionView>("definitions");
  const [data, setData] = React.useState<
    AssetDefinitionData | AssetDefinitionBridgeData | AssetDefinitionGroupSummaryData | null
  >(null);
  const [filters, setFilters] = React.useState<Filters>(EMPTY_FILTERS);
  const [appliedFilters, setAppliedFilters] = React.useState<Filters>(EMPTY_FILTERS);
  const [page, setPage] = React.useState(1);
  const [pageSize, setPageSize] = React.useState(50);
  const [sortField, setSortField] = React.useState("asset_class");
  const [sort, setSort] = React.useState<"asc" | "desc">("asc");
  const [changes, setChanges] = React.useState<Record<string, number | null>>({});
  const [newGroupName, setNewGroupName] = React.useState("");
  const [loading, setLoading] = React.useState(true);
  const [saving, setSaving] = React.useState(false);
  const [creatingGroup, setCreatingGroup] = React.useState(false);
  const [error, setError] = React.useState<string | null>(null);

  const query = React.useMemo(() => {
    const params = new URLSearchParams();
    if (view === "definitions") {
      params.set("page", String(page));
      params.set("page_size", String(pageSize));
      params.set("sort_field", sortField);
      params.set("sort", sort);
      if (appliedFilters.macro) params.set("macro_asset_class", appliedFilters.macro);
      if (appliedFilters.assetClass) params.set("asetClass", appliedFilters.assetClass);
      if (appliedFilters.assetType) params.set("asset_type_title", appliedFilters.assetType);
      if (appliedFilters.groupName) params.set("group_name", appliedFilters.groupName);
    }
    return params;
  }, [appliedFilters, page, pageSize, sort, sortField, view]);

  const load = React.useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const result = await loadAssetDefinitionSettings(tenant, view, query);
      setData(result);
    } catch (loadError) {
      setError(errorMessageOr(loadError, "Asset Definition settings could not be loaded."));
    } finally {
      setLoading(false);
    }
  }, [query, tenant, view]);

  React.useEffect(() => {
    const timer = window.setTimeout(() => void load(), 0);
    return () => window.clearTimeout(timer);
  }, [load]);

  function changeSort(field: string) {
    if (sortField === field) setSort((current) => current === "asc" ? "desc" : "asc");
    else {
      setSortField(field);
      setSort("asc");
    }
    setPage(1);
  }

  function sortIcon(field: string) {
    if (sortField !== field) return null;
    return sort === "asc" ? <ArrowUp className="size-3.5" /> : <ArrowDown className="size-3.5" />;
  }

  async function saveChanges() {
    if (Object.keys(changes).length === 0) {
      toast.info("No Asset Definition changes to save.");
      return;
    }
    setSaving(true);
    try {
      await saveAssetDefinitionAssignments(tenant, changes);
      setChanges({});
      toast.success("Asset Definition saved");
      await load();
    } catch (saveError) {
      toastApiError(saveError, "Asset Definition could not be saved.");
    } finally {
      setSaving(false);
    }
  }

  async function createGroup() {
    const name = newGroupName.trim();
    if (!name) {
      toast.error("Enter a group name.");
      return;
    }
    setCreatingGroup(true);
    try {
      const group = await createAssetDefinitionGroup(tenant, name);
      setData((current) => isDefinitionData(current)
        ? { ...current, groups: [...current.groups, group].sort((a, b) => a.name.localeCompare(b.name)) }
        : current);
      setNewGroupName("");
      toast.success("Group created");
    } catch (createError) {
      toastApiError(createError, "Group could not be created.");
    } finally {
      setCreatingGroup(false);
    }
  }

  async function exportCurrentView() {
    try {
      const params = new URLSearchParams(query);
      params.set("all", "1");
      const exportData = await loadAssetDefinitionSettings(tenant, view, params);
      if (isDefinitionData(exportData)) {
        downloadCsv("asset-definition.csv",
          ["Macro Asset Class", "Asset Class", "Asset Type", "Group Name", "Value"],
          exportData.rows.map((row) => [
            plainLabel(row.macro_asset_class),
            plainLabel(row.asset_class),
            plainLabel(row.asset_type),
            row.group_name || "Not Grouped",
            row.value,
          ]));
      } else if (view === "bridge") {
        const bridge = exportData as AssetDefinitionBridgeData;
        downloadCsv("asset-definition-bridge.csv",
          ["Asset Class", "Asset Type", "Group Name", "Value"],
          bridge.rows.map((row) => [
            plainLabel(row.asset_class),
            plainLabel(row.asset_type),
            row.group_name || "Un Grouped",
            row.value,
          ]));
      } else {
        const summary = exportData as AssetDefinitionGroupSummaryData;
        downloadCsv("asset-definition-group-summary.csv",
          ["Group Name", "Value"],
          summary.rows.map((row) => [row.group_name, row.value]));
      }
    } catch (exportError) {
      toastApiError(exportError, "Export could not be generated.");
    }
  }

  const changeCount = Object.keys(changes).length;
  const hasActiveFilters = Object.values(appliedFilters).some(Boolean);
  const hasDraftFilters = Object.values(filters).some(Boolean);

  function applyFilters() {
    setAppliedFilters(filters);
    setPage(1);
  }

  function clearFilters() {
    setFilters(EMPTY_FILTERS);
    setAppliedFilters(EMPTY_FILTERS);
    setPage(1);
  }

  const filterFields: { key: keyof Filters; label: string; placeholder: string }[] = [
    { key: "macro", label: "Macro asset class", placeholder: "e.g. Equities" },
    { key: "assetClass", label: "Asset class", placeholder: "e.g. Bond" },
    { key: "assetType", label: "Asset type", placeholder: "e.g. Corporate Bond" },
    { key: "groupName", label: "Group name", placeholder: "e.g. Core holdings" },
  ];

  return (
    <div className="space-y-5">
      <div className="flex flex-wrap items-center justify-between gap-3">
        <Tabs value={view} onValueChange={(value) => setView(value as AssetDefinitionView)}>
          <TabsList>
            <TabsTrigger value="definitions">Asset Definition</TabsTrigger>
            <TabsTrigger value="bridge">Bridge Report</TabsTrigger>
            <TabsTrigger value="group-summary">Group Summary</TabsTrigger>
          </TabsList>
        </Tabs>
        <div className="flex gap-2">
          <Button type="button" variant="outline" size="sm" className="gap-2" onClick={() => void load()}>
            <RefreshCw className={cn("size-4", loading && "animate-spin")} /> Refresh
          </Button>
          <Button type="button" variant="outline" size="sm" className="gap-2" onClick={() => void exportCurrentView()}>
            <Download className="size-4" /> Export CSV
          </Button>
        </div>
      </div>

      {view === "definitions" && isDefinitionData(data) && (
        <div className="grid gap-3 sm:grid-cols-3">
          <StatTile
            icon={Rows3}
            label={hasActiveFilters ? "Definitions matching filters" : "Definitions"}
            value={data.pagination.total_count}
          />
          <StatTile icon={FolderCheck} label="Assigned to a group (all definitions)" value={data.totals.grouped_count} />
          <StatTile icon={Layers} label="Groups" value={data.groups.length} />
        </div>
      )}

      {view === "definitions" && (
        <form
          className="space-y-3 rounded-xl border bg-muted/20 p-4"
          onSubmit={(event) => {
            event.preventDefault();
            applyFilters();
          }}
        >
          <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
            {filterFields.map((field) => (
              <label key={field.key} className="space-y-1.5">
                <span className="font-medium text-muted-foreground text-xs">{field.label}</span>
                <Input
                  className="bg-background"
                  value={filters[field.key]}
                  onChange={(event) => setFilters({ ...filters, [field.key]: event.target.value })}
                  placeholder={field.placeholder}
                />
              </label>
            ))}
          </div>
          <div className="flex flex-wrap items-center justify-end gap-2">
            {hasActiveFilters || hasDraftFilters ? (
              <Button type="button" variant="ghost" size="sm" className="gap-1.5" onClick={clearFilters}>
                <X className="size-3.5" /> Clear
              </Button>
            ) : null}
            <Button type="submit" size="sm" className="gap-2">
              <Search className="size-4" /> Apply filters
            </Button>
          </div>
        </form>
      )}

      {view === "definitions" && isDefinitionData(data) && (
        <div className="flex flex-wrap items-center gap-3 rounded-xl border bg-card p-3">
          <span className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
            <FolderPlus className="size-4" />
          </span>
          <Input
            className="max-w-sm"
            value={newGroupName}
            onChange={(event) => setNewGroupName(event.target.value)}
            onKeyDown={(event) => {
              if (event.key === "Enter") {
                event.preventDefault();
                void createGroup();
              }
            }}
            placeholder="New group name"
            aria-label="New group name"
          />
          <Button type="button" variant="secondary" disabled={creatingGroup} onClick={() => void createGroup()}>
            {creatingGroup ? "Creating…" : "Create group"}
          </Button>
          <p className="ml-auto hidden text-muted-foreground text-xs md:block">
            Create a group, then assign it to any row below.
          </p>
        </div>
      )}

      {error && <p className="rounded-xl border border-destructive/30 bg-destructive/5 p-4 text-destructive text-sm">{error}</p>}
      {loading && (
        <div className="space-y-2 rounded-xl border bg-card p-4" aria-label="Loading Asset Definition">
          {Array.from({ length: 6 }, (_, index) => (
            <Skeleton key={index} className="h-9 w-full" />
          ))}
        </div>
      )}

      {!loading && !error && data && (
        <div className="overflow-hidden rounded-xl border bg-card">
          <div className="max-h-[60vh] overflow-auto">
            {isDefinitionData(data) ? (
              <Table>
                <TableHeader className="sticky top-0 z-10">
                  <TableRow className="hover:bg-transparent">
                    <TableHead className={cn(TH, "w-14")}>#</TableHead>
                    {[
                      ["macro_asset_class", "Macro Asset Class"],
                      ["asset_class", "Asset Class"],
                      ["asset_type", "Asset Type"],
                      ["group_name", "Group Name"],
                    ].map(([field, label]) => (
                      <TableHead key={field} className={TH}>
                        <button
                          type="button"
                          className={cn(
                            "flex items-center gap-1 font-medium uppercase tracking-wide hover:text-foreground",
                            sortField === field && "text-foreground",
                          )}
                          onClick={() => changeSort(field)}
                        >
                          {label} {sortIcon(field)}
                        </button>
                      </TableHead>
                    ))}
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {data.rows.map((row, index) => {
                    const rowKey = row.ref_id || `definition-${index}-${row.asset_class}-${row.asset_type}`;
                    const modified = Object.hasOwn(changes, row.ref_id);
                    const selected = modified ? changes[row.ref_id] : row.group_id;
                    const selectedValue =
                      selected === null || selected === undefined || Number(selected) <= 0
                        ? NONE_VALUE
                        : String(selected);
                    return (
                      <TableRow key={rowKey} className={cn(modified && "bg-primary/5 hover:bg-primary/10")}>
                        <TableCell className="text-muted-foreground text-xs tabular-nums">
                          {(data.pagination.page - 1) * data.pagination.page_size + index + 1}
                        </TableCell>
                        <TableCell><LegacyLabel value={row.macro_asset_class} /></TableCell>
                        <TableCell><LegacyLabel value={row.asset_class} /></TableCell>
                        <TableCell className="max-w-md"><LegacyLabel value={row.asset_type} /></TableCell>
                        <TableCell className="min-w-64">
                          <div className="flex items-center gap-2">
                            <Select
                              value={selectedValue}
                              onValueChange={(value) => {
                                if (!row.ref_id) return;
                                setChanges((current) => ({
                                  ...current,
                                  [row.ref_id]: value === NONE_VALUE ? null : Number(value),
                                }));
                              }}
                            >
                              <SelectTrigger
                                className={cn(
                                  "w-full",
                                  selectedValue === NONE_VALUE && "text-muted-foreground",
                                  modified && "border-primary/50",
                                )}
                              >
                                <SelectValue placeholder="Not Grouped" />
                              </SelectTrigger>
                              <SelectContent>
                                <SelectItem value={NONE_VALUE}>Not Grouped</SelectItem>
                                {data.groups
                                  .filter((group) => Number.isFinite(group.id) && group.id > 0)
                                  .map((group) => (
                                    <SelectItem key={group.id} value={String(group.id)}>
                                      {group.name || `Group ${group.id}`}
                                    </SelectItem>
                                  ))}
                              </SelectContent>
                            </Select>
                            {modified ? (
                              <span className="size-2 shrink-0 rounded-full bg-primary" title="Unsaved change" />
                            ) : null}
                          </div>
                        </TableCell>
                      </TableRow>
                    );
                  })}
                  {data.rows.length === 0 && (
                    <TableRow><TableCell colSpan={5} className="h-32 text-center text-muted-foreground">No asset definitions match the filters.</TableCell></TableRow>
                  )}
                </TableBody>
              </Table>
            ) : view === "bridge" ? (
              <Table>
                <TableHeader className="sticky top-0 z-10">
                  <TableRow className="hover:bg-transparent">
                    <TableHead className={cn(TH, "w-14")}>#</TableHead>
                    <TableHead className={TH}>Asset Class</TableHead>
                    <TableHead className={TH}>Asset Type</TableHead>
                    <TableHead className={TH}>Group Name</TableHead>
                    <TableHead className={cn(TH, "text-right")}>Value</TableHead>
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {(data as AssetDefinitionBridgeData).rows.map((row, index) => (
                    <TableRow key={row.ref_id || `bridge-${index}-${row.asset_class}-${row.asset_type}`}>
                      <TableCell className="text-muted-foreground text-xs tabular-nums">{index + 1}</TableCell>
                      <TableCell><LegacyLabel value={row.asset_class} /></TableCell>
                      <TableCell className="max-w-md"><LegacyLabel value={row.asset_type} /></TableCell>
                      <TableCell><GroupName name={row.group_name} fallback="Un Grouped" /></TableCell>
                      <TableCell className="text-right tabular-nums">{number(row.value)}</TableCell>
                    </TableRow>
                  ))}
                </TableBody>
                <TableFooter>
                  <TableRow className="font-semibold">
                    <TableCell colSpan={4}>Total</TableCell>
                    <TableCell className="text-right tabular-nums">{number(data.totals.value)}</TableCell>
                  </TableRow>
                </TableFooter>
              </Table>
            ) : (
              <Table>
                <TableHeader className="sticky top-0 z-10">
                  <TableRow className="hover:bg-transparent">
                    <TableHead className={cn(TH, "w-14")}>#</TableHead>
                    <TableHead className={TH}>Group Name</TableHead>
                    <TableHead className={cn(TH, "text-right")}>Value</TableHead>
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {(data as AssetDefinitionGroupSummaryData).rows.map((row, index) => (
                    <TableRow key={`${row.group_name || "ungrouped"}-${index}`}>
                      <TableCell className="text-muted-foreground text-xs tabular-nums">{index + 1}</TableCell>
                      <TableCell><GroupName name={row.group_name} fallback="Un Grouped" /></TableCell>
                      <TableCell className="text-right tabular-nums">{number(row.value)}</TableCell>
                    </TableRow>
                  ))}
                </TableBody>
                <TableFooter>
                  <TableRow className="font-semibold">
                    <TableCell colSpan={2}>Total</TableCell>
                    <TableCell className="text-right tabular-nums">{number(data.totals.value)}</TableCell>
                  </TableRow>
                </TableFooter>
              </Table>
            )}
          </div>

          {view === "definitions" && isDefinitionData(data) && (
            <div className="flex flex-wrap items-center justify-between gap-3 border-t bg-muted/20 px-4 py-2.5">
              <div className="flex items-center gap-2 text-muted-foreground text-sm">
                <span>Rows per page</span>
                <Select value={String(pageSize)} onValueChange={(value) => { setPageSize(Number(value)); setPage(1); }}>
                  <SelectTrigger className="h-8 w-20 bg-background"><SelectValue /></SelectTrigger>
                  <SelectContent>{[25, 50, 100, 200].map((size) => <SelectItem key={size} value={String(size)}>{size}</SelectItem>)}</SelectContent>
                </Select>
                <span className="tabular-nums">{data.pagination.total_count} total</span>
              </div>
              <div className="flex items-center gap-2">
                <Button type="button" variant="outline" size="icon-sm" aria-label="Previous page" disabled={page <= 1} onClick={() => setPage((current) => current - 1)}><ChevronLeft /></Button>
                <span className="min-w-24 text-center text-sm tabular-nums">Page {data.pagination.page} of {data.pagination.page_count}</span>
                <Button type="button" variant="outline" size="icon-sm" aria-label="Next page" disabled={page >= data.pagination.page_count} onClick={() => setPage((current) => current + 1)}><ChevronRight /></Button>
              </div>
            </div>
          )}
        </div>
      )}

      {view === "definitions" && (
        <div
          className={cn(
            "sticky bottom-0 z-20 flex flex-wrap items-center justify-between gap-3 rounded-xl border bg-background/95 px-4 py-3 backdrop-blur transition-colors",
            changeCount > 0 && "border-primary/40 shadow-md",
          )}
        >
          <p className="text-sm">
            {changeCount > 0 ? (
              <span className="font-medium">
                {changeCount} unsaved {changeCount === 1 ? "change" : "changes"}
              </span>
            ) : (
              <span className="text-muted-foreground">Pick a group on any row to start. Changes are saved together.</span>
            )}
          </p>
          <div className="flex items-center gap-2">
            {changeCount > 0 ? (
              <Button type="button" variant="ghost" className="gap-2" disabled={saving} onClick={() => setChanges({})}>
                <Undo2 className="size-4" /> Discard
              </Button>
            ) : null}
            <Button type="button" className="gap-2" disabled={saving || changeCount === 0} onClick={() => void saveChanges()}>
              <Save className="size-4" /> {saving ? "Saving…" : `Save changes${changeCount ? ` (${changeCount})` : ""}`}
            </Button>
          </div>
        </div>
      )}
    </div>
  );
}
