"use client";

import { toastApiError } from "@/lib/toast-api-error";
import * as React from "react";
import { resolveCustomerTenant } from "@/app/customer/_lib/resolve-customer-tenant";
import Link from "next/link";
import { useParams } from "next/navigation";
import { Layers3, RefreshCw, X } from "lucide-react";
import { toast } from "sonner";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import {
  getInitialAssetAllocationFilters,
  saveUserReportSettings,
  loadUserReportSettings,
} from "@/lib/report-user-settings";

import {
  AssetAllocationFiltersDialog,
  AssetAllocationReportTypeMenu,
} from "./asset-allocation-filters-dialog";
import { AssetAllocationMatrix } from "./asset-allocation-matrix";
import {
  type AssetAllocationFilters,
  type AssetAllocationHolding,
  type AssetAllocationReportType,
  type AssetAllocationSortOrder,
  assetAllocationReportTypeOptions,
  defaultAssetAllocationFilters,
  isFullBankSelection,
  normalizeStoredBankSelection,
  queryBankIdsForApi,
} from "./schema";
import {
  type AllocationViewMode,
  ASSET_ALLOCATION_SCHEMA_ORDER,
  buildAllocationMatrixByBank,
  buildAllocationMatrixByClass,
  formatUsd,
  resolveBankDisplayName,
} from "./utils";
import { ReportLoadingPanel } from "@/app/customer/[tenant]/reports/_shared/components/report-loading-panel";
import { exportAssetAllocationPdf } from "../../_lib/asset-allocation-api";
import { useAssetAllocationList } from "../../_lib/use-asset-allocation-list";
import { useDriftAllocationReport } from "../../_lib/use-drift-allocation-report";
import { DriftAllocationPanel, DriftGrandTotalStrip } from "./drift-allocation-panel";
import { formatPlainAmount } from "@/lib/format/numbers";

function viewModeOptionsForReport(
  reportType: AssetAllocationReportType,
): { id: AllocationViewMode; label: string }[] {
  const byClassLabel =
    reportType === "macro" ? "by Macro Class" : reportType === "bridge" ? "by Bridge" : "by Asset Class";
  return [
    { id: "by-class", label: byClassLabel },
    { id: "by-bank", label: "by Bank" },
  ];
}

function reportPageTitle(reportType: AssetAllocationReportType): string {
  switch (reportType) {
    case "macro":
      return "Macro - Asset Allocation Report";
    case "bridge":
      return "Bridge - Asset Allocation Report";
    case "drift":
      return "Actual vs Ideal - Asset Allocation Report";
    default:
      return "Asset Allocation Report";
  }
}

function segmentClass(active: boolean) {
  return cn(
    "h-8 shrink-0 rounded-md px-3 font-semibold text-xs transition-colors",
    active
      ? "bg-emerald-600 text-white shadow-sm"
      : "text-muted-foreground hover:bg-background/80 hover:text-foreground",
  );
}

function filterHoldings(
  holdings: AssetAllocationHolding[],
  filters: AssetAllocationFilters,
  bankOptionIds: string[],
) {
  if (!bankOptionIds.length) return holdings;
  if (filters.banks.length === 0 || isFullBankSelection(filters.banks, bankOptionIds)) {
    return holdings;
  }

  return holdings.filter((row) => {
    const rowBankId = String(row.bankId ?? "").trim();
    return rowBankId !== "" && filters.banks.includes(rowBankId);
  });
}

function GrandTotalStrip({
  total,
  bankCount,
  rowCount,
  positions,
  reportingCurrency,
}: {
  total: number;
  bankCount: number;
  rowCount: number;
  positions: number;
  reportingCurrency: string;
}) {
  return (
    <div className="overflow-hidden rounded-xl border border-border/80 bg-card shadow-sm">
      <div className="flex items-center gap-x-4 gap-y-1 overflow-x-auto px-4 py-2.5 sm:px-5">
        <span className="shrink-0 font-semibold text-[11px] text-emerald-700 uppercase tracking-wider dark:text-emerald-400">
          Grand total
        </span>
        <p className="shrink-0 whitespace-nowrap text-sm tabular-nums">
          <span className="font-bold text-foreground">{reportingCurrency || "USD"}</span>
          <span className="text-muted-foreground"> </span>
          <span className="font-semibold text-emerald-700 dark:text-emerald-400">
            {formatUsd(total)}
          </span>
        </p>
        <span className="shrink-0 whitespace-nowrap text-muted-foreground text-sm tabular-nums">
          <span className="font-semibold text-foreground">{bankCount}</span> banks
        </span>
        <span className="shrink-0 whitespace-nowrap text-muted-foreground text-sm tabular-nums">
          <span className="font-semibold text-foreground">{rowCount}</span> rows
        </span>
        <span className="shrink-0 whitespace-nowrap text-muted-foreground text-sm tabular-nums">
          <span className="font-semibold text-foreground">{positions}</span> positions
        </span>
      </div>
    </div>
  );
}

export function AssetAllocationReportView() {
  const params = useParams<{ tenant?: string }>();
  const tenant = typeof params?.tenant === "string" && params.tenant.trim() ? params.tenant.trim() : resolveCustomerTenant();
  const [filters, setFilters] = React.useState<AssetAllocationFilters>(defaultAssetAllocationFilters);
  const [settingsReady, setSettingsReady] = React.useState(false);
  const [viewMode, setViewMode] = React.useState<AllocationViewMode>("by-class");
  const [refreshKey, setRefreshKey] = React.useState(0);
  const [isExportingPdf, setIsExportingPdf] = React.useState(false);
  const [appliedDriftBankIds, setAppliedDriftBankIds] = React.useState<string[]>([]);
  const banksSyncedRef = React.useRef(false);

  React.useEffect(() => {
    setFilters(getInitialAssetAllocationFilters());
    setSettingsReady(true);
  }, []);

  const isDriftReport = filters.reportType === "drift";

  const {
    rows: holdings,
    bankOptions: matrixBankOptions,
    assetClassOptions,
    bankTotals,
    reportingCurrency,
    grandTotal,
    isLoading: isMatrixLoading,
    errorMessage: matrixErrorMessage,
  } = useAssetAllocationList([], filters.reportType, refreshKey, settingsReady && !isDriftReport);

  const {
    data: driftData,
    bankOptions: driftBankOptions,
    isLoading: isDriftLoading,
    errorMessage: driftErrorMessage,
  } = useDriftAllocationReport(
    isDriftReport ? appliedDriftBankIds : [],
    refreshKey,
    settingsReady && isDriftReport,
  );

  const bankOptions = isDriftReport ? driftBankOptions : matrixBankOptions;
  const isLoading = isDriftReport ? isDriftLoading : isMatrixLoading;
  const errorMessage = isDriftReport ? driftErrorMessage : matrixErrorMessage;

  const viewModeOptions = React.useMemo(
    () => viewModeOptionsForReport(filters.reportType),
    [filters.reportType],
  );
  const pageTitle = React.useMemo(() => reportPageTitle(filters.reportType), [filters.reportType]);

  const bankOptionIds = React.useMemo(
    () => bankOptions.map((option) => option.id),
    [bankOptions],
  );
  const bankOptionLabelById = React.useMemo(
    () =>
      Object.fromEntries(
        bankOptions.map((option) => [option.id, resolveBankDisplayName(option.id, option.name)]),
      ),
    [bankOptions],
  );

  React.useEffect(() => {
    if (!settingsReady || banksSyncedRef.current) return;
    const optionIds = isDriftReport
      ? driftBankOptions.map((option) => option.id)
      : bankOptionIds;
    if (!optionIds.length) return;
    banksSyncedRef.current = true;
    setFilters((prev) => ({
      ...prev,
      banks: isDriftReport
        ? [...optionIds]
        : normalizeStoredBankSelection(prev.banks, optionIds),
    }));
  }, [settingsReady, bankOptionIds, driftBankOptions, isDriftReport]);

  const effectiveBanks = React.useMemo(() => {
    const fromOptions = bankOptionIds.length
      ? filters.banks.length === 0 || isFullBankSelection(filters.banks, bankOptionIds)
        ? [...bankOptionIds]
        : filters.banks.filter((bankId) => bankOptionIds.includes(bankId))
      : [];

    if (fromOptions.length) return fromOptions;

    return Array.from(new Set(holdings.map((row) => row.bankId || row.bank).filter(Boolean)));
  }, [filters.banks, bankOptionIds, holdings]);

  const liveAssetClasses = React.useMemo(
    () => Array.from(new Set(holdings.map((row) => row.assetClass).filter(Boolean))),
    [holdings],
  );

  const effectiveAssetClasses = React.useMemo(() => {
    // Macro / Bridge: Yii1 rows come from the full module/group list (incl. empty).
    if (filters.reportType === "macro" || filters.reportType === "bridge") {
      if (assetClassOptions.length > 0) return assetClassOptions;
      return liveAssetClasses;
    }
    const extras = liveAssetClasses.filter(
      (c) => !(ASSET_ALLOCATION_SCHEMA_ORDER as readonly string[]).includes(c),
    );
    return [...ASSET_ALLOCATION_SCHEMA_ORDER, ...extras];
  }, [filters.reportType, liveAssetClasses, assetClassOptions]);

  // Yii1 always renders every $asset_modules row (empty cells included).
  const includeEmptyRows = true;

  const effectiveFilters = React.useMemo(
    () => ({
      ...filters,
      banks: effectiveBanks,
      assetClasses: effectiveAssetClasses,
    }),
    [filters, effectiveBanks, effectiveAssetClasses],
  );

  const bankColumns = React.useMemo(
    () =>
      effectiveBanks.map((bankId) => {
        const fromOptions = bankOptionLabelById[bankId];
        const fromHolding = holdings.find((row) => row.bankId === bankId || row.bank === bankId);
        return {
          id: bankId,
          label: resolveBankDisplayName(bankId, fromOptions || fromHolding?.bank),
        };
      }),
    [effectiveBanks, bankOptionLabelById, holdings],
  );

  React.useEffect(() => {
    const syncFromSettings = () => {
      banksSyncedRef.current = false;
      setFilters(getInitialAssetAllocationFilters());
    };
    window.addEventListener("report-settings-updated", syncFromSettings);
    return () => window.removeEventListener("report-settings-updated", syncFromSettings);
  }, []);

  const filteredHoldings = React.useMemo(
    () =>
      filterHoldings(holdings, effectiveFilters, bankOptionIds.length ? bankOptionIds : effectiveBanks),
    [holdings, effectiveFilters, bankOptionIds, effectiveBanks],
  );

  const matrix = React.useMemo(() => {
    const matrixOptions = {
      includeEmptyRows,
      bankTotalsById: bankTotals,
      grandTotal,
    };
    if (viewMode === "by-bank") {
      return buildAllocationMatrixByBank(
        filteredHoldings,
        bankColumns,
        effectiveFilters.assetClasses,
        matrixOptions,
      );
    }
    return buildAllocationMatrixByClass(
      filteredHoldings,
      bankColumns,
      effectiveFilters.assetClasses,
      matrixOptions,
    );
  }, [
    filteredHoldings,
    bankColumns,
    effectiveFilters.assetClasses,
    viewMode,
    includeEmptyRows,
    bankTotals,
    grandTotal,
  ]);

  const rowLabel =
    viewMode === "by-bank"
      ? "Bank"
      : filters.reportType === "macro"
        ? "Macro Class"
        : filters.reportType === "bridge"
          ? "Bridge"
          : "Asset class";
  const totalsColumnLabel = viewMode === "by-bank" ? "All Asset Class" : "All Banks";
  const activeReportTypeLabel =
    assetAllocationReportTypeOptions.find((option) => option.id === filters.reportType)?.label ??
    "Default";

  const persistFilters = (next: AssetAllocationFilters) => {
    const settings = loadUserReportSettings();
    saveUserReportSettings({
      ...settings,
      assetAllocation: next,
    });
  };

  const handleApplyFilters = (
    nextFilters: AssetAllocationFilters,
    _nextSort: AssetAllocationSortOrder,
  ) => {
    const optionIds = bankOptionIds.length ? bankOptionIds : effectiveBanks;
    const selectedBanks = normalizeStoredBankSelection(nextFilters.banks, optionIds);
    const applied = {
      ...nextFilters,
      reportType: filters.reportType,
      banks: selectedBanks,
    };
    banksSyncedRef.current = true;
    setFilters(applied);
    if (isDriftReport) {
      setAppliedDriftBankIds(queryBankIdsForApi(selectedBanks, optionIds));
    }
    setRefreshKey((key) => key + 1);
    persistFilters(applied);
    toast.success("Asset allocation report generated", {
      description: `${applied.banks.length} bank${applied.banks.length === 1 ? "" : "s"} selected.`,
    });
  };

  const handleReportTypeChange = (reportType: AssetAllocationReportType) => {
    if (reportType === filters.reportType) return;
    banksSyncedRef.current = false;
    const next = {
      ...filters,
      reportType,
      ...(reportType === "drift" ? { banks: [] as string[] } : {}),
    };
    if (reportType === "drift") {
      setAppliedDriftBankIds([]);
    }
    setFilters(next);
    setRefreshKey((key) => key + 1);
    persistFilters(next);
  };

  const handleExportPdf = async (banks: string[]) => {
    try {
      setIsExportingPdf(true);
      await exportAssetAllocationPdf(banks, filters.reportType);
      toast.success("PDF downloaded");
    } catch (error) {
      toastApiError(error, "PDF export failed.");
    } finally {
      setIsExportingPdf(false);
    }
  };

  return (
    <div className="flex min-w-0 flex-col gap-5">
      <div className="flex flex-col gap-3 border-b pb-4">
        <div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
          <div className="min-w-0 space-y-2">
            <div className="flex flex-wrap items-center gap-2.5">
              <div className="flex size-8 items-center justify-center rounded-lg border bg-emerald-500/10 text-emerald-700 dark:text-emerald-400">
                <Layers3 className="size-4" />
              </div>
              <h1 className="font-semibold text-2xl tracking-tight leading-none">{pageTitle}</h1>
              <Badge variant="secondary" className="font-normal tabular-nums">
                {isDriftReport
                  ? formatPlainAmount(driftData?.banks.length ?? 0)
                  : formatPlainAmount(matrix.columns.length)}{" "}
                banks
              </Badge>
              <Badge variant="outline" className="font-normal">
                {activeReportTypeLabel}
              </Badge>
              {isLoading ? (
                <Badge variant="outline" className="font-normal">
                  Loading…
                </Badge>
              ) : null}
            </div>

            {!isDriftReport ? (
              <div
                className="inline-flex gap-0.5 rounded-lg border border-border/60 bg-muted/40 p-1"
                role="tablist"
                aria-label="Allocation view"
              >
                {viewModeOptions.map((opt) => (
                  <button
                    key={opt.id}
                    type="button"
                    role="tab"
                    aria-selected={viewMode === opt.id}
                    onClick={() => setViewMode(opt.id)}
                    className={segmentClass(viewMode === opt.id)}
                  >
                    {opt.label}
                  </button>
                ))}
              </div>
            ) : null}

            {errorMessage ? <p className="text-destructive text-sm">{errorMessage}</p> : null}
          </div>

          <div className="flex shrink-0 flex-wrap items-center gap-2">
            <AssetAllocationReportTypeMenu
              reportType={filters.reportType}
              onChange={handleReportTypeChange}
            />
            <AssetAllocationFiltersDialog
              filters={filters}
              sortOrder={null}
              onApply={handleApplyFilters}
              onExportPdf={handleExportPdf}
              isExportingPdf={isExportingPdf}
              hidePdfExport={isDriftReport}
              bankOptions={
                bankOptions.length
                  ? bankOptions
                  : effectiveBanks.map((id) => ({
                      id,
                      name: bankOptionLabelById[id] || id,
                    }))
              }
              isLoadingBanks={
                isLoading &&
                bankOptions.length === 0 &&
                (isDriftReport ? !driftData : holdings.length === 0)
              }
            />
            <Button
              variant="outline"
              size="icon"
              className="size-9 shrink-0"
              disabled={isLoading}
              onClick={() => {
                const resetBanks = bankOptionIds.length ? [...bankOptionIds] : [...effectiveBanks];
                banksSyncedRef.current = true;
                if (isDriftReport) {
                  setAppliedDriftBankIds([]);
                }
                setFilters({
                  ...getInitialAssetAllocationFilters(),
                  reportType: filters.reportType,
                  banks: resetBanks,
                });
                setRefreshKey((key) => key + 1);
                toast.success("Report refreshed");
              }}
            >
              <RefreshCw className={cn("size-4", isLoading && "animate-spin")} />
              <span className="sr-only">Refresh</span>
            </Button>
            <Button variant="outline" size="icon" className="size-9 shrink-0" asChild>
              <Link href={`/customer/${tenant}/reports`}>
                <X className="size-4" />
                <span className="sr-only">Close</span>
              </Link>
            </Button>
          </div>
        </div>
      </div>

      {isDriftReport ? (
        <DriftGrandTotalStrip data={driftData} isLoading={isLoading} />
      ) : (
        <GrandTotalStrip
          total={grandTotal || matrix.grandTotal}
          bankCount={matrix.columns.length}
          rowCount={matrix.rows.length}
          positions={filteredHoldings.length}
          reportingCurrency={reportingCurrency}
        />
      )}

      {isDriftReport ? (
        <DriftAllocationPanel
          tenant={tenant}
          data={driftData}
          isLoading={isLoading}
          errorMessage={errorMessage}
        />
      ) : null}

      {!isDriftReport ? (
        <ReportLoadingPanel
          loading={isLoading}
          label="Loading asset allocation…"
          className={cn(
            "overflow-hidden rounded-xl border border-border/80 bg-card shadow-sm",
            isLoading && "pointer-events-none",
          )}
        >
          {matrix.columns.length > 0 || filteredHoldings.length > 0 ? (
            <AssetAllocationMatrix
              matrix={matrix}
              rowLabel={rowLabel}
              totalsColumnLabel={totalsColumnLabel}
            />
          ) : !isLoading ? (
            <div className="p-10 text-center text-sm text-muted-foreground">
              No allocation data for the current filters.
            </div>
          ) : (
            <div className="h-full min-h-[calc(100vh-280px)]" aria-hidden />
          )}
        </ReportLoadingPanel>
      ) : null}
    </div>
  );
}
