"use client";

import { toastApiError } from "@/lib/toast-api-error";
import * as React from "react";
import { resolveCustomerTenant } from "@/app/customer/_lib/resolve-customer-tenant";
import { presetForDefaultLayout } from "@/app/customer/_lib/default-layout";
import { useDefaultLayout } from "@/app/customer/_lib/use-default-layout";
import Link from "next/link";
import { useParams } from "next/navigation";
import { Layers3, RefreshCw, X } from "lucide-react";
import { toast } from "sonner";
import { isUserDrivenNow, notifyTutorialAction } from "@/components/customer-tutorial/tutorial-actions";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import {
  getInitialAssetAllocationFilters,
  hasExplicitAssetAllocationReportTypePreference,
  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,
  banksForPicker,
  defaultAssetAllocationFilters,
  isFullBankSelection,
  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 { useReportBankModeInvalidation } from "@/app/customer/_lib/use-report-bank-mode-invalidation";
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 || 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({ canViewDrift = true }: { canViewDrift?: boolean } = {}) {
  const params = useParams<{ tenant?: string }>();
  const tenant = typeof params?.tenant === "string" && params.tenant.trim() ? params.tenant.trim() : resolveCustomerTenant();
  const { layout, loading: layoutLoading } = useDefaultLayout(tenant);
  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 previousLayoutRef = React.useRef<string | null>(null);
  const skipSettingsSyncRef = React.useRef(false);

  React.useEffect(() => {
    if (layoutLoading) return;
    const initial = getInitialAssetAllocationFilters();
    // Merge logic: combine improved layout/sticky/URL logic and canViewDrift gating.
    const layoutPreset = presetForDefaultLayout(layout).assetAllocationType;
    const layoutChanged =
      previousLayoutRef.current !== null && previousLayoutRef.current !== layout;
    previousLayoutRef.current = layout;

    // Sticky AA mode only applies while common Layout matches the layout it was
    // chosen under. Any common Layout change (or a stale sticky from a prior
    // layout) falls back to the layout preset.
    const sticky = !layoutChanged && hasExplicitAssetAllocationReportTypePreference(layout);
    let reportType = sticky ? initial.reportType : layoutPreset;

    // If drift is selected but user cannot view drift, force fallback.
    if (reportType === "drift" && !canViewDrift) {
      reportType = "default";
    }

    setFilters({
      ...initial,
      reportType,
    });
    setSettingsReady(true);

    if (layoutChanged || !sticky) {
      const settings = loadUserReportSettings();
      const needsPersist =
        layoutChanged ||
        settings.assetAllocation.reportType !== reportType ||
        settings.assetAllocationReportTypeChosen === true;
      if (needsPersist) {
        skipSettingsSyncRef.current = true;
        saveUserReportSettings({
          ...settings,
          assetAllocation: { ...settings.assetAllocation, ...initial, reportType, banks: [] },
          assetAllocationReportTypeChosen: sticky,
          assetAllocationReportTypeChosenLayout: sticky
            ? settings.assetAllocationReportTypeChosenLayout
            : undefined,
        });
      }
      if (layoutChanged) {
        if (reportType !== "drift") {
          setAppliedDriftBankIds([]);
        }
        setRefreshKey((key) => key + 1);
      }
    }
  }, [layout, layoutLoading, canViewDrift]);

  React.useEffect(() => {
    const syncFromSettings = () => {
      if (skipSettingsSyncRef.current) {
        skipSettingsSyncRef.current = false;
        return;
      }
      const initial = getInitialAssetAllocationFilters();
      setFilters((current) => ({
        ...initial,
        banks: current.banks,
        // Use persisted reportType (not stale React state) so a Layout-driven
        // save is not overwritten by this listener racing setState.
        reportType: initial.reportType,
      }));
    };
    window.addEventListener("report-settings-updated", syncFromSettings);
    return () => window.removeEventListener("report-settings-updated", syncFromSettings);
  }, []);

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

  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 = React.useMemo(() => {
    const source = isDriftReport ? driftBankOptions : matrixBankOptions;
    return source.filter((option) => {
      const id = String(option.id ?? "").trim();
      const name = String(option.name ?? "").trim();
      return id !== "" && id !== "-" && name !== "-";
    });
  }, [driftBankOptions, isDriftReport, 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],
  );

  const effectiveBanks = React.useMemo(() => {
    if (bankOptionIds.length) return banksForPicker(filters.banks, bankOptionIds);
    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],
  );

  // Parent/Child bank mode changed in Settings: the bank picker's id-space
  // (parent ids vs child ids) is no longer valid — drop the stale selection
  // and refetch so the dropdown repopulates in the new mode.
  useReportBankModeInvalidation(() => {
    setFilters((current) => ({ ...current, banks: [] }));
    setAppliedDriftBankIds([]);
    setRefreshKey((key) => key + 1);
  });

  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, opts?: { reportTypeChosen?: boolean }) => {
    const settings = loadUserReportSettings();
    saveUserReportSettings({
      ...settings,
      assetAllocation: { ...next, banks: [] },
      ...(opts?.reportTypeChosen
        ? {
            assetAllocationReportTypeChosen: true,
            assetAllocationReportTypeChosenLayout: layout,
          }
        : {}),
    });
  };

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

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

  const handleExportPdf = async (banks: string[]) => {
    const userDriven = isUserDrivenNow();
    try {
      setIsExportingPdf(true);
      await exportAssetAllocationPdf(banks, filters.reportType);
      toast.success("PDF downloaded");
      notifyTutorialAction("report-exported", { userDriven });
    } 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}
              canViewDrift={canViewDrift}
            />
            <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={() => {
                if (isDriftReport) {
                  setAppliedDriftBankIds([]);
                }
                setFilters({
                  ...getInitialAssetAllocationFilters(),
                  reportType: filters.reportType,
                  banks: [],
                });
                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>
  );
}
