import { MockReconciliationEngine, type ReconciliationSnapshot } from '@dbis/integration-foundation';
import axios from 'axios';
import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
import { fetchFineractGlAccounts } from './omnl-ipsas-gl';
import { fetchOmnlComplianceAggregated } from './omnl-compliance';
import { appendOmnlAudit } from './omnl-audit-log';
import { loadXauReconcileBaseline } from './omnl-compliance-pack';

export interface TripleReconcileBreak {
  code: string;
  severity: 'critical' | 'high' | 'medium' | 'low';
  message: string;
  fineract?: unknown;
  onChain?: unknown;
  custodian?: unknown;
}

export interface TripleReconcileReport {
  generatedAt: string;
  lineId: string;
  breaks: TripleReconcileBreak[];
  aligned: boolean;
  /** Optional HYBX↔Fineract↔chain ref reconcile via integration-foundation (OMNL_FOUNDATION_RECONCILE=1). */
  foundationSnapshot?: ReconciliationSnapshot;
  fineract: {
    glSnapshot: Array<{ glCode: string; name?: string; id: number }>;
    liabilityGlCodes: string[];
  };
  onChain: {
    r: string;
    s0: string;
    s1: string;
    reportingCompliant: boolean;
    attestationStale: boolean;
  } | null;
  custodian: {
    source: string;
    xauOunces?: string;
    asOf?: string;
    porAttestationRef?: string;
  } | null;
  comparison?: {
    custodianOz?: number;
    onChainROz?: number;
    deltaOz?: number;
    deltaPercent?: number;
    withinTolerance?: boolean;
  } | null;
}

function loadCustodianSnapshot(): {
  xauOunces?: string;
  asOf?: string;
  source: string;
  porAttestationRef?: string;
} | null {
  const envOz = process.env.OMNL_CUSTODIAN_RESERVE_XAU_OZ?.trim();
  if (envOz) {
    return { source: 'env:OMNL_CUSTODIAN_RESERVE_XAU_OZ', xauOunces: envOz, asOf: new Date().toISOString() };
  }
  const file =
    process.env.OMNL_CUSTODIAN_SNAPSHOT_PATH?.trim() ||
    resolve(process.cwd(), 'config/omnl-custodian-snapshot.json');
  if (!existsSync(file)) return null;
  try {
    const j = JSON.parse(readFileSync(file, 'utf8')) as {
      xauOunces?: string;
      asOf?: string;
      porAttestationRef?: string;
    };
    return {
      source: file,
      xauOunces: j.xauOunces,
      asOf: j.asOf,
      porAttestationRef: j.porAttestationRef,
    };
  } catch {
    return null;
  }
}

/** Bank/custodian ↔ Fineract GL ↔ on-chain reserve R — operational reconciliation (not statutory sign-off). */
export async function runTripleStateReconcile(lineId?: string): Promise<TripleReconcileReport> {
  const line = lineId || process.env.OMNL_HEALTH_LINE_ID?.trim() || '';
  const breaks: TripleReconcileBreak[] = [];
  const generatedAt = new Date().toISOString();

  let glRows: Awaited<ReturnType<typeof fetchFineractGlAccounts>> = [];
  try {
    glRows = await fetchFineractGlAccounts();
  } catch (e) {
    breaks.push({
      code: 'FINERACT_UNREACHABLE',
      severity: 'critical',
      message: e instanceof Error ? e.message : String(e),
    });
  }

  const liabilityCodes = ['2000', '2100', '1050', '1000', '21010'];
  const glSnapshot = glRows
    .filter((r) => r.glCode && liabilityCodes.includes(String(r.glCode)))
    .map((r) => ({ glCode: String(r.glCode), name: r.name, id: r.id }));

  let onChain: TripleReconcileReport['onChain'] = null;
  if (!line) {
    breaks.push({
      code: 'LINE_ID_MISSING',
      severity: 'high',
      message: 'Set OMNL_HEALTH_LINE_ID or pass lineId query param',
    });
  } else {
    try {
      const snap = await fetchOmnlComplianceAggregated(line);
      onChain = {
        r: snap.r,
        s0: snap.s0,
        s1: snap.s1,
        reportingCompliant: snap.reportingCompliant,
        attestationStale: snap.attestationStale,
      };
      if (!snap.reportingCompliant) {
        breaks.push({
          code: 'ONCHAIN_NOT_REPORTING_COMPLIANT',
          severity: 'high',
          message: 'ComplianceCore reportingCompliant is false',
          onChain,
        });
      }
      if (snap.attestationStale) {
        breaks.push({
          code: 'ONCHAIN_ATTESTATION_STALE',
          severity: 'medium',
          message: 'Reserve attestation past validUntil',
          onChain,
        });
      }
    } catch (e) {
      breaks.push({
        code: 'ONCHAIN_READ_FAILED',
        severity: 'high',
        message: e instanceof Error ? e.message : String(e),
      });
    }
  }

  const custodian = loadCustodianSnapshot();
  if (!custodian?.xauOunces) {
    breaks.push({
      code: 'CUSTODIAN_SNAPSHOT_MISSING',
      severity: 'medium',
      message: 'Set OMNL_CUSTODIAN_RESERVE_XAU_OZ or config/omnl-custodian-snapshot.json',
    });
  }

  let comparison: TripleReconcileReport['comparison'] = null;
  const baseline = loadXauReconcileBaseline();
  const ratioStr =
    process.env.OMNL_XAU_R_TO_OZ_RATIO?.trim() ||
    (baseline?.rMinorUnitsPerTroyOunce
      ? String(1 / Number(baseline.rMinorUnitsPerTroyOunce))
      : '');
  if (custodian?.xauOunces && onChain?.r && ratioStr) {
    const custOz = parseFloat(custodian.xauOunces);
    const rMinor = parseFloat(onChain.r);
    const ratio = parseFloat(ratioStr);
    if (Number.isFinite(custOz) && Number.isFinite(rMinor) && Number.isFinite(ratio) && ratio > 0) {
      const onChainOz = rMinor * ratio;
      const deltaOz = custOz - onChainOz;
      const deltaPercent = onChainOz !== 0 ? (Math.abs(deltaOz) / onChainOz) * 100 : 0;
      const tol = Number(baseline?.tolerancePercent ?? 2.5);
      comparison = {
        custodianOz: custOz,
        onChainROz: onChainOz,
        deltaOz,
        deltaPercent,
        withinTolerance: deltaPercent <= tol,
      };
      if (!comparison.withinTolerance) {
        breaks.push({
          code: 'CUSTODIAN_ONCHAIN_DELTA_EXCEEDED',
          severity: 'high',
          message: `Custodian vs on-chain XAU delta ${deltaPercent.toFixed(2)}% exceeds tolerance ${tol}%`,
          custodian,
          onChain,
        });
      }
    }
  } else if (custodian?.xauOunces && onChain?.r && !ratioStr) {
    breaks.push({
      code: 'CUSTODIAN_CHAIN_RATIO_UNCONFIGURED',
      severity: 'medium',
      message: 'Set OMNL_XAU_R_TO_OZ_RATIO or config/compliance/gru-xau-reconcile-baseline.v1.json',
      custodian,
      onChain,
    });
  }

  if (glSnapshot.length === 0 && glRows.length > 0) {
    breaks.push({
      code: 'FINERACT_CORE_GL_MISSING',
      severity: 'high',
      message: 'Expected GL codes 1000/1050/2000/2100 not all present in Fineract',
      fineract: { count: glRows.length },
    });
  }

  const aligned = breaks.filter((b) => b.severity === 'critical' || b.severity === 'high').length === 0;

  let foundationSnapshot: ReconciliationSnapshot | undefined;
  if (process.env.OMNL_FOUNDATION_RECONCILE === '1') {
    const hybxRefs = (process.env.OMNL_RECON_HYBX_REFS ?? '')
      .split(',')
      .map((s) => s.trim())
      .filter(Boolean);
    const fineractRefs = (process.env.OMNL_RECON_FINERACT_REFS ?? '')
      .split(',')
      .map((s) => s.trim())
      .filter(Boolean);
    const chainRefs = (process.env.OMNL_RECON_CHAIN_REFS ?? line)
      .split(',')
      .map((s) => s.trim())
      .filter(Boolean);
    const engine = new MockReconciliationEngine();
    foundationSnapshot = await engine.reconcileTripleState({
      tenantId: process.env.OMNL_FINERACT_TENANT?.trim() || 'omnl',
      entityId: process.env.OMNL_RECON_ENTITY_ID?.trim() || 'omnl-default',
      hybxRefs,
      fineractRefs,
      chainRefs,
    });
    for (const brk of foundationSnapshot.breaks) {
      breaks.push({
        code: `FOUNDATION_${brk.type.toUpperCase()}`,
        severity: brk.type === 'missing_event' ? 'high' : 'medium',
        message: `${brk.source}: ${brk.referenceId}`,
      });
    }
  }

  const report: TripleReconcileReport = {
    generatedAt,
    lineId: line,
    breaks,
    aligned: breaks.filter((b) => b.severity === 'critical' || b.severity === 'high').length === 0,
    fineract: { glSnapshot, liabilityGlCodes: liabilityCodes },
    onChain,
    custodian,
    comparison,
    foundationSnapshot,
  };

  appendOmnlAudit({
    category: 'reconcile',
    action: 'triple_state_reconcile',
    traceId: line || undefined,
    status: aligned ? 'ok' : 'error',
    metadata: { breakCount: breaks.length, aligned },
  });

  return report;
}
