import { Contract, JsonRpcProvider, type InterfaceAbi } from 'ethers';
import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
import { isCompliant, maxM1ForM0, minReservesForM0 } from './omnl-policy-math';
import { omnlComplianceCore138, omnlReserveStore138 } from './omnl-chain138-addresses';

const COMPLIANCE_ABI: InterfaceAbi = [
  'function getCompliance(bytes32 lineId) view returns (uint256 s0, uint256 s1, uint256 r, uint256 validUntil, bytes32 evidenceHash, bytes32 merkleRoot, uint256 minR, uint256 maxS1, bool m0Ok, bool m1Ok, bool attestationStale, bool policyOk, bool operational, bool reportingCompliant)',
];

const BREAKER_ABI: InterfaceAbi = ['function isLineOperational(bytes32 lineId) view returns (bool)'];

const ERC20_ABI: InterfaceAbi = ['function totalSupply() view returns (uint256)'];

const RESERVE_ABI: InterfaceAbi = [
  'function getCommitment(bytes32 lineId) view returns (tuple(uint256 R,uint256 validUntil,bytes32 evidenceHash,bytes32 merkleRoot,uint256 version))',
];

export interface OmnlComplianceSnapshot {
  chainId: number;
  lineId: string;
  s0: string;
  s1: string;
  r: string;
  validUntil: string;
  evidenceHash: string;
  merkleRoot: string;
  minR: string;
  maxS1: string;
  m0Ok: boolean;
  m1Ok: boolean;
  attestationStale: boolean;
  policyOk: boolean;
  operational: boolean;
  reportingCompliant: boolean;
}

export interface OmnlCrossChainLineEntry {
  lineId: string;
  chains: Record<string, { tokenM0: string; tokenM1: string }>;
}

export interface OmnlAggregatedSnapshot extends OmnlComplianceSnapshot {
  aggregated: true;
  s0ByChain: Record<string, string>;
  s1ByChain: Record<string, string>;
}

function rpcForChain(chainId: number): string | undefined {
  if (chainId === 138) {
    return process.env.RPC_URL_138 || process.env.RPC_URL;
  }
  if (chainId === 651940) {
    return process.env.CHAIN_651940_RPC_URL || 'https://mainnet-rpc.alltra.global';
  }
  return process.env[`CHAIN_${chainId}_RPC_URL`];
}

function parseTuple(res: unknown): OmnlComplianceSnapshot & { chainId: number; lineId: string } {
  const t = res as unknown as {
    s0: bigint;
    s1: bigint;
    r: bigint;
    validUntil: bigint;
    evidenceHash: string;
    merkleRoot: string;
    minR: bigint;
    maxS1: bigint;
    m0Ok: boolean;
    m1Ok: boolean;
    attestationStale: boolean;
    policyOk: boolean;
    operational: boolean;
    reportingCompliant: boolean;
  };
  return {
    chainId: 0,
    lineId: '',
    s0: t.s0.toString(),
    s1: t.s1.toString(),
    r: t.r.toString(),
    validUntil: t.validUntil.toString(),
    evidenceHash: t.evidenceHash,
    merkleRoot: t.merkleRoot,
    minR: t.minR.toString(),
    maxS1: t.maxS1.toString(),
    m0Ok: t.m0Ok,
    m1Ok: t.m1Ok,
    attestationStale: t.attestationStale,
    policyOk: t.policyOk,
    operational: t.operational,
    reportingCompliant: t.reportingCompliant,
  };
}

export async function fetchOmnlCompliance(
  chainId: number,
  lineIdHex: string,
  complianceAddress: string
): Promise<OmnlComplianceSnapshot> {
  const url = rpcForChain(chainId);
  if (!url) {
    throw new Error(`No RPC configured for chain ${chainId}`);
  }
  const provider = new JsonRpcProvider(url, chainId);
  const c = new Contract(complianceAddress, COMPLIANCE_ABI, provider);
  const lineId = lineIdHex.startsWith('0x') ? lineIdHex : `0x${lineIdHex}`;
  const res = await c.getCompliance(lineId);
  const out = parseTuple(res);
  out.chainId = chainId;
  out.lineId = lineId;
  return out;
}

export function loadCrossChainConfigPath(): string {
  return (
    process.env.OMNL_CROSS_CHAIN_CONFIG ||
    resolve(__dirname, '../../../../config/hybx-omnl-cross-chain-lines.json')
  );
}

export function loadCrossChainLines(): OmnlCrossChainLineEntry[] {
  const p = loadCrossChainConfigPath();
  if (!existsSync(p)) {
    return [];
  }
  const raw = JSON.parse(readFileSync(p, 'utf8')) as { lines?: OmnlCrossChainLineEntry[] };
  return raw.lines ?? [];
}

async function readTotalSupply(rpcUrl: string, chainId: number, token: string): Promise<bigint> {
  const provider = new JsonRpcProvider(rpcUrl, chainId);
  const t = new Contract(token, ERC20_ABI, provider);
  return (await t.totalSupply()) as bigint;
}

/**
 * Sum M0/M1 supply across chains for a logical line; use R/TTL from primary (138) reserve or compliance.
 */
export async function fetchOmnlComplianceAggregated(lineIdHex: string): Promise<OmnlAggregatedSnapshot> {
  const addr138 = omnlComplianceCore138();
  if (!addr138) {
    throw new Error('OMNL_COMPLIANCE_CORE_138 or OMNL_COMPLIANCE_CORE_V2_138 required for aggregated view');
  }

  const lineId = lineIdHex.startsWith('0x') ? lineIdHex : `0x${lineIdHex}`;
  const lines = loadCrossChainLines();
  const entry = lines.find((l) => l.lineId.toLowerCase() === lineId.toLowerCase());
  if (!entry) {
    throw new Error(`lineId not found in cross-chain config: ${lineId}`);
  }

  const primary = await fetchOmnlCompliance(138, lineId, addr138);
  const rpc138 = rpcForChain(138)!;
  const rpc651 = rpcForChain(651940)!;

  let s0Sum = 0n;
  let s1Sum = 0n;
  const s0ByChain: Record<string, string> = {};
  const s1ByChain: Record<string, string> = {};

  for (const [cid, pair] of Object.entries(entry.chains)) {
    const chainNum = parseInt(cid, 10);
    const rpc = chainNum === 138 ? rpc138 : chainNum === 651940 ? rpc651 : rpcForChain(chainNum);
    if (!rpc) continue;
    const a0 = await readTotalSupply(rpc, chainNum, pair.tokenM0);
    const a1 = await readTotalSupply(rpc, chainNum, pair.tokenM1);
    s0Sum += a0;
    s1Sum += a1;
    s0ByChain[cid] = a0.toString();
    s1ByChain[cid] = a1.toString();
  }

  const r = BigInt(primary.r);
  const validUntil = BigInt(primary.validUntil);
  const now = BigInt(Math.floor(Date.now() / 1000));
  const attestationStale = now > validUntil;
  const minR = minReservesForM0(s0Sum);
  const maxS1 = maxM1ForM0(s0Sum);
  const m0Ok = r >= minR;
  const m1Ok = s1Sum <= maxS1;
  const policyOk = isCompliant(s0Sum, s1Sum, r);

  let operational = primary.operational;
  const br651 = process.env.OMNL_CIRCUIT_BREAKER_651940;
  if (br651) {
    const p = new JsonRpcProvider(rpc651, 651940);
    const b = new Contract(br651, BREAKER_ABI, p);
    operational = operational && (await b.isLineOperational(lineId));
  }

  const reportingCompliant = policyOk && !attestationStale && operational;

  return {
    aggregated: true,
    chainId: 138,
    lineId,
    s0: s0Sum.toString(),
    s1: s1Sum.toString(),
    r: primary.r,
    validUntil: primary.validUntil,
    evidenceHash: primary.evidenceHash,
    merkleRoot: primary.merkleRoot,
    minR: minR.toString(),
    maxS1: maxS1.toString(),
    m0Ok,
    m1Ok,
    attestationStale,
    policyOk,
    operational,
    reportingCompliant,
    s0ByChain,
    s1ByChain,
  };
}

/** Latest commitment tuple from ReserveCommitmentStore (when OMNL_RESERVE_STORE_* set). */
export async function fetchLatestAttestation(
  chainId: number,
  lineIdHex: string
): Promise<{ r: string; validUntil: string; evidenceHash: string; merkleRoot: string; version: string }> {
  const addr =
    chainId === 138
      ? omnlReserveStore138()
      : chainId === 651940
        ? process.env.OMNL_RESERVE_STORE_651940
        : process.env[`OMNL_RESERVE_STORE_${chainId}`];
  if (!addr) {
    throw new Error(`OMNL_RESERVE_STORE_${chainId} not configured`);
  }
  const url = rpcForChain(chainId);
  if (!url) throw new Error(`No RPC for ${chainId}`);
  const lineId = lineIdHex.startsWith('0x') ? lineIdHex : `0x${lineIdHex}`;
  const c = new Contract(addr, RESERVE_ABI, new JsonRpcProvider(url, chainId));
  const t = await c.getCommitment(lineId);
  const x = t as unknown as { R: bigint; validUntil: bigint; evidenceHash: string; merkleRoot: string; version: bigint };
  return {
    r: x.R.toString(),
    validUntil: x.validUntil.toString(),
    evidenceHash: x.evidenceHash,
    merkleRoot: x.merkleRoot,
    version: x.version.toString(),
  };
}

export async function fetchBreakerStatus(chainId: number, lineIdHex: string): Promise<{
  globalPaused: boolean;
  linePaused: boolean;
  operational: boolean;
}> {
  const addr =
    chainId === 138
      ? process.env.OMNL_CIRCUIT_BREAKER_138
      : chainId === 651940
        ? process.env.OMNL_CIRCUIT_BREAKER_651940
        : process.env[`OMNL_CIRCUIT_BREAKER_${chainId}`];
  if (!addr) {
    throw new Error(`OMNL_CIRCUIT_BREAKER_${chainId} not configured`);
  }
  const url = rpcForChain(chainId);
  if (!url) throw new Error(`No RPC for ${chainId}`);
  const lineId = lineIdHex.startsWith('0x') ? lineIdHex : `0x${lineIdHex}`;
  const abi: InterfaceAbi = [
    'function globalPaused() view returns (bool)',
    'function linePaused(bytes32) view returns (bool)',
    'function isLineOperational(bytes32 lineId) view returns (bool)',
  ];
  const b = new Contract(addr, abi, new JsonRpcProvider(url, chainId));
  const [globalPaused, linePaused, operational] = await Promise.all([
    b.globalPaused(),
    b.linePaused(lineId),
    b.isLineOperational(lineId),
  ]);
  return { globalPaused, linePaused, operational };
}
