import { readFileSync, existsSync } from 'fs';
import { hoLiquidityCacheMs } from '../config/omnl-ho-liquidity-cache';
import { resolve } from 'path';
import { Contract, JsonRpcProvider, formatUnits } from 'ethers';
import { getChain138IssuedCStarTokens } from '../config/chain138-gru-cstar-tokens';
import { getCanonicalPriceUsd } from './canonical-price-oracle';
import { getCachedOfficeSignedGlBalances, fineractConfigured } from './omnl-fineract-office-gl';
import { loadNostroPolicy } from './omnl-cash-in-bank-audit';

const CHAIN_138 = 138;
const ERC20_TOTAL_SUPPLY_ABI = ['function totalSupply() view returns (uint256)'];

export type HoReserveCoverageStatus = 'sufficient' | 'insufficient' | 'pending' | 'unavailable';

export interface HoReserveCoverageInfo {
  status: HoReserveCoverageStatus;
  multiplier: number;
  headOfficeId: number;
  reserveAssetGlCodes: string[];
  reserveAssetsUsd: number;
  issuedCStarUsd: number;
  requiredReserveUsd: number;
  coverageRatio: number;
  formula: string;
  policyId: string;
  policyRef: string;
  generatedAt: string;
  tokenCount?: number;
  error?: string;
}

type ReserveCoveragePolicy = {
  policyId?: string;
  headOfficeId?: number;
  reserveAssetGlCodes?: string[];
  reserveCoverageMultiplier?: number;
  supplyNostroMatchMultiplier?: number;
};

let cache: { expiresAt: number; snapshot: HoReserveCoverageInfo } | null = null;
let inflight: Promise<HoReserveCoverageInfo> | null = null;

function repoRoot(): string {
  if (process.env.PROXMOX_ROOT?.trim()) return process.env.PROXMOX_ROOT.trim();
  if (process.env.PHOENIX_REPO_ROOT?.trim()) return process.env.PHOENIX_REPO_ROOT.trim();
  return resolve(__dirname, '../../../../..');
}

export function loadReserveCoveragePolicy(): {
  policyId: string;
  headOfficeId: number;
  reserveAssetGlCodes: string[];
  multiplier: number;
  policyRef: string;
} {
  const nostro = loadNostroPolicy();
  const candidates = [
    process.env.OMNL_NOSTRO_POLICY_PATH?.trim(),
    resolve(repoRoot(), 'config/compliance/omnl-nostro-backed-netting.v1.json'),
    resolve(__dirname, '../../../../config/compliance/omnl-nostro-backed-netting.v1.json'),
  ].filter(Boolean) as string[];
  const path = candidates.find((c) => existsSync(c));
  if (!path) {
    return {
      policyId: nostro.policyId,
      headOfficeId: nostro.headOfficeId,
      reserveAssetGlCodes: ['1000', '1050'],
      multiplier: nostro.supplyNostroMatchMultiplier,
      policyRef: nostro.policyRef,
    };
  }
  const parsed = JSON.parse(readFileSync(path, 'utf8')) as ReserveCoveragePolicy;
  return {
    policyId: String(parsed.policyId || nostro.policyId),
    headOfficeId: Number(parsed.headOfficeId ?? nostro.headOfficeId),
    reserveAssetGlCodes: Array.isArray(parsed.reserveAssetGlCodes) && parsed.reserveAssetGlCodes.length
      ? parsed.reserveAssetGlCodes.map(String)
      : ['1000', '1050'],
    multiplier: Number(
      parsed.reserveCoverageMultiplier ?? parsed.supplyNostroMatchMultiplier ?? nostro.supplyNostroMatchMultiplier,
    ),
    policyRef: path.replace(`${repoRoot()}/`, ''),
  };
}

export function reserveAssetsUsdFromGlRows(
  rows: Array<{ glCode: string; side: string; amount: string }>,
  reserveAssetGlCodes: string[],
): number {
  let total = 0;
  for (const code of reserveAssetGlCodes) {
    const row = rows.find((r) => r.glCode === code);
    if (!row || row.side !== 'Dr') continue;
    const amountUsd = Number(row.amount);
    if (Number.isFinite(amountUsd) && amountUsd > 0) {
      total += amountUsd;
    }
  }
  return total;
}

export function evaluateHoReserveCoverage(input: {
  reserveAssetsUsd?: number;
  issuedCStarUsd?: number;
  multiplier?: number;
  headOfficeId?: number;
  reserveAssetGlCodes?: string[];
  tokenCount?: number;
}): HoReserveCoverageInfo {
  const policy = loadReserveCoveragePolicy();
  const multiplier = input.multiplier ?? policy.multiplier;
  const headOfficeId = input.headOfficeId ?? policy.headOfficeId;
  const reserveAssetGlCodes = input.reserveAssetGlCodes ?? policy.reserveAssetGlCodes;
  const generatedAt = new Date().toISOString();
  const formula = `coverageRatio = reserveAssetsUsd / (${multiplier} × issuedCStarUsd)`;

  if (input.issuedCStarUsd == null || !(input.issuedCStarUsd > 0)) {
    return {
      status: 'pending',
      multiplier,
      headOfficeId,
      reserveAssetGlCodes,
      reserveAssetsUsd: input.reserveAssetsUsd ?? 0,
      issuedCStarUsd: input.issuedCStarUsd ?? 0,
      requiredReserveUsd: 0,
      coverageRatio: 0,
      formula,
      policyId: policy.policyId,
      policyRef: policy.policyRef,
      generatedAt,
      tokenCount: input.tokenCount,
    };
  }

  if (input.reserveAssetsUsd == null || !(input.reserveAssetsUsd > 0)) {
    return {
      status: 'unavailable',
      multiplier,
      headOfficeId,
      reserveAssetGlCodes,
      reserveAssetsUsd: input.reserveAssetsUsd ?? 0,
      issuedCStarUsd: input.issuedCStarUsd,
      requiredReserveUsd: input.issuedCStarUsd * multiplier,
      coverageRatio: 0,
      formula,
      policyId: policy.policyId,
      policyRef: policy.policyRef,
      generatedAt,
      tokenCount: input.tokenCount,
      error: 'OMNL Fineract reserve GL balances unavailable',
    };
  }

  const requiredReserveUsd = input.issuedCStarUsd * multiplier;
  const coverageRatio = input.reserveAssetsUsd / requiredReserveUsd;
  const status: HoReserveCoverageStatus = coverageRatio >= 1 ? 'sufficient' : 'insufficient';

  return {
    status,
    multiplier,
    headOfficeId,
    reserveAssetGlCodes,
    reserveAssetsUsd: input.reserveAssetsUsd,
    issuedCStarUsd: input.issuedCStarUsd,
    requiredReserveUsd,
    coverageRatio,
    formula,
    policyId: policy.policyId,
    policyRef: policy.policyRef,
    generatedAt,
    tokenCount: input.tokenCount,
  };
}

function chain138RpcUrl(): string {
  return (
    process.env.RPC_URL_138?.trim()
    || process.env.CHAIN_138_RPC_URL?.trim()
    || 'http://192.168.11.211:8545'
  );
}

async function resolveIssuedCStarUsd(): Promise<{ issuedCStarUsd: number; tokenCount: number }> {
  const tokens = getChain138IssuedCStarTokens();
  if (tokens.length === 0) {
    return { issuedCStarUsd: 0, tokenCount: 0 };
  }

  const provider = new JsonRpcProvider(chain138RpcUrl(), CHAIN_138);
  const rows = await Promise.all(
    tokens.map(async (spec) => {
      const address = String(spec.addresses[CHAIN_138]).trim();
      try {
        const contract = new Contract(address, ERC20_TOTAL_SUPPLY_ABI, provider);
        const raw = await contract.totalSupply() as bigint;
        if (raw <= 0n) return 0;
        const units = Number(formatUnits(raw, spec.decimals));
        if (!Number.isFinite(units) || units <= 0) return 0;
        const priceUsd = getCanonicalPriceUsd(CHAIN_138, address) ?? 1;
        return units * priceUsd;
      } catch {
        return 0;
      }
    }),
  );

  return {
    issuedCStarUsd: rows.reduce((sum, value) => sum + value, 0),
    tokenCount: tokens.length,
  };
}

export async function buildHoReserveCoverageSnapshot(options?: {
  officeId?: number;
  forceRefreshGl?: boolean;
}): Promise<HoReserveCoverageInfo> {
  const policy = loadReserveCoveragePolicy();
  const officeId = options?.officeId
    ?? Number(process.env.OMNL_CASH_IN_BANK_OFFICE_ID || process.env.OMNL_DEFAULT_OFFICE_ID || policy.headOfficeId);

  let issuedCStarUsd = 0;
  let tokenCount = 0;
  try {
    const issued = await resolveIssuedCStarUsd();
    issuedCStarUsd = issued.issuedCStarUsd;
    tokenCount = issued.tokenCount;
  } catch {
    issuedCStarUsd = 0;
  }

  if (!fineractConfigured()) {
    return evaluateHoReserveCoverage({
      reserveAssetsUsd: 0,
      issuedCStarUsd,
      headOfficeId: officeId,
      tokenCount,
    });
  }

  let reserveAssetsUsd = 0;
  try {
    const glRows = await getCachedOfficeSignedGlBalances(officeId, { forceRefresh: options?.forceRefreshGl });
    reserveAssetsUsd = reserveAssetsUsdFromGlRows(glRows, policy.reserveAssetGlCodes);
  } catch (e) {
    return {
      ...evaluateHoReserveCoverage({
        reserveAssetsUsd: 0,
        issuedCStarUsd,
        headOfficeId: officeId,
        tokenCount,
      }),
      error: e instanceof Error ? e.message : String(e),
    };
  }

  return evaluateHoReserveCoverage({
    reserveAssetsUsd,
    issuedCStarUsd,
    headOfficeId: officeId,
    tokenCount,
  });
}

/** Cached HO reserve coverage vs aggregate Chain 138 c* issuance (default TTL 120s). */
export async function getCachedHoReserveCoverageSnapshot(options?: {
  forceRefresh?: boolean;
  officeId?: number;
}): Promise<HoReserveCoverageInfo> {
  const ttlMs = Number(process.env.OMNL_RESERVE_COVERAGE_CACHE_MS || hoLiquidityCacheMs());
  const now = Date.now();
  if (!options?.forceRefresh && cache && cache.expiresAt > now) {
    return cache.snapshot;
  }
  if (inflight) {
    return inflight;
  }
  inflight = buildHoReserveCoverageSnapshot({ officeId: options?.officeId })
    .then((snapshot) => {
      cache = { expiresAt: Date.now() + ttlMs, snapshot };
      return snapshot;
    })
    .finally(() => {
      inflight = null;
    });
  return inflight;
}

export function resetHoReserveCoverageCache(): void {
  cache = null;
  inflight = null;
}
