import { getCachedHoReserveCoverageSnapshot, type HoReserveCoverageInfo } from './chain138-ho-reserve-coverage';
import {
  evaluateSupplyNostroMatch,
  getCachedCashInBankAuditSnapshot,
  loadNostroPolicy,
  type CashInBankAuditSnapshot,
  type SupplyNostroMatchInfo,
} from './omnl-cash-in-bank-audit';
import { fineractConfigured } from './omnl-fineract-office-gl';
import {
  loadCusdcPerTokenSample,
  loadHoLiquidityTripleStateSummary,
  type HoLiquidityPerTokenSample,
  type HoLiquidityTripleStateSummary,
} from './omnl-ho-liquidity-enrichment';

export type HoLiquidityAlertSeverity = 'critical' | 'warning' | 'info';

export interface HoLiquidityAlert {
  code: string;
  severity: HoLiquidityAlertSeverity;
  message: string;
}

export interface HoLiquiditySnapshot {
  generatedAt: string;
  officeId: number;
  policyId: string;
  policyRef: string;
  fineractConfigured: boolean;
  cashInBank: CashInBankAuditSnapshot;
  reserveCoverage: HoReserveCoverageInfo;
  aggregateSupplyNostroMatch?: SupplyNostroMatchInfo;
  perTokenSamples?: {
    cUSDC?: HoLiquidityPerTokenSample;
  };
  tripleState?: HoLiquidityTripleStateSummary;
  alerts: HoLiquidityAlert[];
  auditMaxAgeMinutes: number;
}

function auditMaxAgeMinutes(): number {
  return Number(process.env.OMNL_HO_NOSTRO_AUDIT_MAX_AGE_MINUTES || 45);
}

export function evaluateHoLiquidityAlerts(input: {
  cashInBank: CashInBankAuditSnapshot;
  reserveCoverage: HoReserveCoverageInfo;
  generatedAt: string;
}): HoLiquidityAlert[] {
  const alerts: HoLiquidityAlert[] = [];
  const { cashInBank, reserveCoverage, generatedAt } = input;

  if (!fineractConfigured()) {
    alerts.push({
      code: 'FINERACT_NOT_CONFIGURED',
      severity: 'critical',
      message: 'OMNL_FINERACT_BASE_URL / OMNL_FINERACT_PASSWORD not set on token-aggregation',
    });
  }

  if (cashInBank.audit.status === 'unavailable' || reserveCoverage.status === 'unavailable') {
    alerts.push({
      code: 'FINERACT_FETCH_UNAVAILABLE',
      severity: 'critical',
      message: cashInBank.audit.error || reserveCoverage.error || 'Fineract HO GL snapshot unavailable',
    });
  }

  if (reserveCoverage.status === 'insufficient') {
    alerts.push({
      code: 'RESERVE_COVERAGE_INSUFFICIENT',
      severity: 'critical',
      message: `Reserve coverage ${reserveCoverage.coverageRatio.toFixed(2)}× below 1.0× required`,
    });
  } else if (
    reserveCoverage.coverageRatio > 0
    && reserveCoverage.coverageRatio < Number(process.env.OMNL_HO_RESERVE_COVERAGE_WARN_RATIO || 1.2)
  ) {
    alerts.push({
      code: 'RESERVE_COVERAGE_LOW',
      severity: 'warning',
      message: `Reserve coverage ${reserveCoverage.coverageRatio.toFixed(2)}× below ${Number(process.env.OMNL_HO_RESERVE_COVERAGE_WARN_RATIO || 1.2)}× policy target`,
    });
  }

  const ageMin = (Date.now() - new Date(generatedAt).getTime()) / 60_000;
  if (ageMin > auditMaxAgeMinutes()) {
    alerts.push({
      code: 'AUDIT_STALE',
      severity: 'warning',
      message: `Snapshot age ${ageMin.toFixed(0)}m exceeds ${auditMaxAgeMinutes()}m watchdog threshold`,
    });
  }

  if (cashInBank.audit.supplyNostroMatch === 'break') {
    alerts.push({
      code: 'NOSTRO_SUPPLY_BREAK',
      severity: 'info',
      message: 'Per-token supply vs 1.2× nostro settlement mismatch (expected for large cUSDC until nostro funded)',
    });
  }

  return alerts;
}

function patchCashInBankAuditWithAggregateMatch(
  cashInBank: CashInBankAuditSnapshot,
  aggregateMatch: SupplyNostroMatchInfo,
): CashInBankAuditSnapshot {
  const supplyStatus = aggregateMatch.status === 'matched'
    ? 'aligned'
    : aggregateMatch.status === 'break'
      ? 'breaks'
      : cashInBank.audit.status;
  return {
    ...cashInBank,
    audit: {
      ...cashInBank.audit,
      status: supplyStatus,
      supplyNostroMatch: aggregateMatch.status,
      breaksCount: aggregateMatch.status === 'break' ? 1 : 0,
    },
    supplyNostroMatch: aggregateMatch,
  };
}

export async function buildHoLiquiditySnapshot(options?: {
  officeId?: number;
  forceRefresh?: boolean;
  lineId?: string;
}): Promise<HoLiquiditySnapshot> {
  const policy = loadNostroPolicy();
  const officeId = options?.officeId
    ?? Number(process.env.OMNL_CASH_IN_BANK_OFFICE_ID || process.env.OMNL_DEFAULT_OFFICE_ID || policy.headOfficeId);
  const forceRefresh = options?.forceRefresh === true;

  const [cashInBank, reserveCoverage] = await Promise.all([
    getCachedCashInBankAuditSnapshot({
      officeId,
      forceRefresh,
      skipTripleState: true,
      lineId: options?.lineId,
    }),
    getCachedHoReserveCoverageSnapshot({ officeId, forceRefresh }),
  ]);

  const aggregateSupplyNostroMatch = evaluateSupplyNostroMatch({
    totalSupplyUsd: reserveCoverage.issuedCStarUsd,
    externalNostroCashUsd: cashInBank.cashInBankUsd,
    headOfficeId: officeId,
  });
  const cashInBankEnriched = patchCashInBankAuditWithAggregateMatch(cashInBank, aggregateSupplyNostroMatch);

  const generatedAt = new Date(
    Math.max(
      new Date(cashInBankEnriched.generatedAt).getTime(),
      new Date(reserveCoverage.generatedAt).getTime(),
    ),
  ).toISOString();

  const [tripleState, cusdcSample] = await Promise.all([
    loadHoLiquidityTripleStateSummary().catch(() => undefined),
    loadCusdcPerTokenSample({
      cashInBankUsd: cashInBankEnriched.cashInBankUsd,
      officeId,
    }).catch(() => undefined),
  ]);

  return {
    generatedAt,
    officeId,
    policyId: policy.policyId,
    policyRef: policy.policyRef,
    fineractConfigured: fineractConfigured(),
    cashInBank: cashInBankEnriched,
    reserveCoverage,
    aggregateSupplyNostroMatch,
    perTokenSamples: cusdcSample ? { cUSDC: cusdcSample } : undefined,
    tripleState,
    alerts: evaluateHoLiquidityAlerts({ cashInBank: cashInBankEnriched, reserveCoverage, generatedAt }),
    auditMaxAgeMinutes: auditMaxAgeMinutes(),
  };
}

let fileSnapshot: HoLiquiditySnapshot | null = null;

export function loadPersistedHoLiquiditySnapshot(): HoLiquiditySnapshot | null {
  return fileSnapshot;
}

export function persistHoLiquiditySnapshot(snapshot: HoLiquiditySnapshot): void {
  fileSnapshot = snapshot;
}
