import fs from 'fs';
import os from 'os';
import path from 'path';
import { resolveTokenMappingLoaderPath } from './repo-config-loader';
import { getRouteFromRegistry } from './cross-chain-bridges';
import {
  filterPoolsForExposure,
  filterPoolsForRouting,
  getActiveTransportPairs,
  getGasAssetFamilies,
  getGasProtocolExposure,
  getGasRedeemGroups,
  getGruTransportMetadata,
  isGasRedemptionPathAllowed,
  isPublicPoolActive,
  isPublicPoolRoutable,
} from './gru-transport';

describe('GRU Transport overlay', () => {
  const originalChain138Bridge = process.env.CHAIN138_L1_BRIDGE;
  const originalBscBridge = process.env.CW_BRIDGE_BSC;
  const originalStableBridge = process.env.CCIP_STABLE_BRIDGE_CHAIN138;
  const originalStablecoinBridge = process.env.CCIP_STABLECOIN_BRIDGE_CHAIN138;
  const originalUniversalBridge = process.env.UNIVERSAL_CCIP_BRIDGE_ADDRESS;
  const originalUniversalBridgeAlt = process.env.UNIVERSAL_CCIP_BRIDGE;
  const originalReserveVerifier = process.env.CW_RESERVE_VERIFIER_CHAIN138;
  const originalReserveVault = process.env.CW_STABLECOIN_RESERVE_VAULT;
  const originalReserveSystem = process.env.CW_RESERVE_SYSTEM;
  const originalMaxOutstanding = process.env.CW_MAX_OUTSTANDING_USDT_BSC;
  const originalGasStrictVerifier = process.env.CW_GAS_STRICT_ESCROW_VERIFIER_CHAIN138;
  const originalGasHybridVerifier = process.env.CW_GAS_HYBRID_CAP_VERIFIER_CHAIN138;
  const originalGasEscrowVault = process.env.CW_GAS_ESCROW_VAULT_CHAIN138;
  const originalGasTreasurySystem = process.env.CW_GAS_TREASURY_SYSTEM;
  const originalMainnetBridge = process.env.CW_BRIDGE_MAINNET;
  const originalOptimismBridge = process.env.CW_BRIDGE_OPTIMISM;
  const originalEthMainnetOutstanding = process.env.CW_MAX_OUTSTANDING_ETH_MAINNET_MAINNET;
  const originalEthMainnetSupply = process.env.CW_GAS_OUTSTANDING_ETH_MAINNET_MAINNET;
  const originalEthMainnetEscrowed = process.env.CW_GAS_ESCROWED_ETH_MAINNET_MAINNET;
  const originalEthMainnetTreasury = process.env.CW_GAS_TREASURY_BACKED_ETH_MAINNET_MAINNET;
  const originalEthL2Outstanding = process.env.CW_MAX_OUTSTANDING_ETH_L2_OPTIMISM;
  const originalEthL2Supply = process.env.CW_GAS_OUTSTANDING_ETH_L2_OPTIMISM;
  const originalEthL2Escrowed = process.env.CW_GAS_ESCROWED_ETH_L2_OPTIMISM;
  const originalEthL2Treasury = process.env.CW_GAS_TREASURY_BACKED_ETH_L2_OPTIMISM;
  const originalEthL2Cap = process.env.CW_GAS_TREASURY_CAP_ETH_L2_OPTIMISM;
  const originalTokenMappingLoaderPath = process.env.TOKEN_MAPPING_LOADER_PATH;
  const originalCwL1Bridge = process.env.CW_L1_BRIDGE;
  const originalCwL1BridgeChain138 = process.env.CW_L1_BRIDGE_CHAIN138;

  afterEach(() => {
    if (originalChain138Bridge === undefined) {
      delete process.env.CHAIN138_L1_BRIDGE;
    } else {
      process.env.CHAIN138_L1_BRIDGE = originalChain138Bridge;
    }

    if (originalCwL1Bridge === undefined) {
      delete process.env.CW_L1_BRIDGE;
    } else {
      process.env.CW_L1_BRIDGE = originalCwL1Bridge;
    }
    if (originalCwL1BridgeChain138 === undefined) {
      delete process.env.CW_L1_BRIDGE_CHAIN138;
    } else {
      process.env.CW_L1_BRIDGE_CHAIN138 = originalCwL1BridgeChain138;
    }

    if (originalBscBridge === undefined) {
      delete process.env.CW_BRIDGE_BSC;
    } else {
      process.env.CW_BRIDGE_BSC = originalBscBridge;
    }

    if (originalStableBridge === undefined) {
      delete process.env.CCIP_STABLE_BRIDGE_CHAIN138;
    } else {
      process.env.CCIP_STABLE_BRIDGE_CHAIN138 = originalStableBridge;
    }

    if (originalStablecoinBridge === undefined) {
      delete process.env.CCIP_STABLECOIN_BRIDGE_CHAIN138;
    } else {
      process.env.CCIP_STABLECOIN_BRIDGE_CHAIN138 = originalStablecoinBridge;
    }

    if (originalUniversalBridge === undefined) {
      delete process.env.UNIVERSAL_CCIP_BRIDGE_ADDRESS;
    } else {
      process.env.UNIVERSAL_CCIP_BRIDGE_ADDRESS = originalUniversalBridge;
    }

    if (originalUniversalBridgeAlt === undefined) {
      delete process.env.UNIVERSAL_CCIP_BRIDGE;
    } else {
      process.env.UNIVERSAL_CCIP_BRIDGE = originalUniversalBridgeAlt;
    }

    if (originalReserveVerifier === undefined) {
      delete process.env.CW_RESERVE_VERIFIER_CHAIN138;
    } else {
      process.env.CW_RESERVE_VERIFIER_CHAIN138 = originalReserveVerifier;
    }

    if (originalReserveVault === undefined) {
      delete process.env.CW_STABLECOIN_RESERVE_VAULT;
    } else {
      process.env.CW_STABLECOIN_RESERVE_VAULT = originalReserveVault;
    }

    if (originalReserveSystem === undefined) {
      delete process.env.CW_RESERVE_SYSTEM;
    } else {
      process.env.CW_RESERVE_SYSTEM = originalReserveSystem;
    }

    if (originalMaxOutstanding === undefined) {
      delete process.env.CW_MAX_OUTSTANDING_USDT_BSC;
    } else {
      process.env.CW_MAX_OUTSTANDING_USDT_BSC = originalMaxOutstanding;
    }

    for (const [key, value] of Object.entries({
      CW_GAS_STRICT_ESCROW_VERIFIER_CHAIN138: originalGasStrictVerifier,
      CW_GAS_HYBRID_CAP_VERIFIER_CHAIN138: originalGasHybridVerifier,
      CW_GAS_ESCROW_VAULT_CHAIN138: originalGasEscrowVault,
      CW_GAS_TREASURY_SYSTEM: originalGasTreasurySystem,
      CW_BRIDGE_MAINNET: originalMainnetBridge,
      CW_BRIDGE_OPTIMISM: originalOptimismBridge,
      CW_MAX_OUTSTANDING_ETH_MAINNET_MAINNET: originalEthMainnetOutstanding,
      CW_GAS_OUTSTANDING_ETH_MAINNET_MAINNET: originalEthMainnetSupply,
      CW_GAS_ESCROWED_ETH_MAINNET_MAINNET: originalEthMainnetEscrowed,
      CW_GAS_TREASURY_BACKED_ETH_MAINNET_MAINNET: originalEthMainnetTreasury,
      CW_MAX_OUTSTANDING_ETH_L2_OPTIMISM: originalEthL2Outstanding,
      CW_GAS_OUTSTANDING_ETH_L2_OPTIMISM: originalEthL2Supply,
      CW_GAS_ESCROWED_ETH_L2_OPTIMISM: originalEthL2Escrowed,
      CW_GAS_TREASURY_BACKED_ETH_L2_OPTIMISM: originalEthL2Treasury,
      CW_GAS_TREASURY_CAP_ETH_L2_OPTIMISM: originalEthL2Cap,
    })) {
      if (value === undefined) {
        delete process.env[key];
      } else {
        process.env[key] = value;
      }
    }

    if (originalTokenMappingLoaderPath === undefined) {
      delete process.env.TOKEN_MAPPING_LOADER_PATH;
    } else {
      process.env.TOKEN_MAPPING_LOADER_PATH = originalTokenMappingLoaderPath;
    }
  });

  it('loads GRU Monetary Transport Layer metadata and active transport pairs', () => {
    const metadata = getGruTransportMetadata();
    expect(metadata).not.toBeNull();
    expect(metadata?.system?.name).toBe('GRU Monetary Transport Layer');
    expect(metadata?.system?.shortName).toBe('GRU Transport');

    const pairs = getActiveTransportPairs();
    expect(pairs.length).toBe(44);
    expect(pairs.every((pair) => pair.eligible)).toBe(true);
    expect(pairs.every((pair) => typeof pair.runtimeReady === 'boolean')).toBe(true);
    expect(pairs.some((pair) => pair.key === '138-25-cUSDT-cWUSDT')).toBe(true);
    expect(pairs.some((pair) => pair.key === '138-1-cBTC-cWBTC')).toBe(true);
    expect(pairs.some((pair) => pair.key === '138-10-cETHL2-cWETHL2')).toBe(true);
    expect(pairs.some((pair) => pair.key === '138-1111-cWEMIX-cWWEMIX')).toBe(false);
    const bscUsdt = pairs.find((pair) => pair.key === '138-56-cUSDT-cWUSDT');
    const mainnetEth = pairs.find((pair) => pair.key === '138-1-cETH-cWETH');
    expect(bscUsdt?.bridgeCanonicalAssetVersion).toBe('v1');
    expect(bscUsdt?.bridgeMirroredAssetVersion).toBe('v1');
    expect(bscUsdt?.destinationChainName).toBe('BSC');
    expect(bscUsdt?.destinationChainSelector).toBe('11344663589394136015');
    expect(mainnetEth?.destinationChainSelector).toBe('5009297550715157269');
    expect(metadata?.counts.enabledDestinationChains).toBe(10);
    expect(metadata?.counts.configuredTransportPairs).toBe(45);
    expect(metadata?.counts.deferredTransportPairs).toBe(1);
    expect(metadata?.counts.gasAssetFamilies).toBe(9);
    expect(metadata?.counts.gasTransportPairs).toBe(10);
  });

  it('publishes gas-family metadata and enforces the ETH split via redeem groups', () => {
    const gasFamilies = getGasAssetFamilies();
    const redeemGroups = getGasRedeemGroups();
    const protocolExposure = getGasProtocolExposure();

    expect(gasFamilies).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          familyKey: 'eth_mainnet',
          backingMode: 'strict_escrow',
          mirroredSymbol: 'cWETH',
        }),
        expect.objectContaining({
          familyKey: 'eth_l2',
          backingMode: 'hybrid_cap',
          mirroredSymbol: 'cWETHL2',
        }),
      ])
    );
    expect(redeemGroups).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          familyKey: 'eth_l2',
          allowedChains: [10, 42161, 8453],
        }),
      ])
    );
    expect(protocolExposure).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          key: '10-eth_l2',
          chainId: 10,
          familyKey: 'eth_l2',
        }),
        expect.objectContaining({
          key: '1111-wemix',
          active: false,
          status: 'deferred',
        }),
      ])
    );
    expect(gasFamilies).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          familyKey: 'wemix',
          active: false,
          status: 'deferred',
        }),
      ])
    );
    expect(isGasRedemptionPathAllowed(10, 42161, 'eth_l2')).toBe(true);
    expect(isGasRedemptionPathAllowed(1, 10, 'eth_mainnet')).toBe(false);
  });

  it('keeps Chain 138 pools visible but hides inactive public cW pools', () => {
    const pools = [
      {
        poolAddress: '0x1111111111111111111111111111111111111111',
        token0Address: '0x9a1D0dBEE997929ED02fD19E0E199704d20914dB',
        token1Address: '0x55d398326f99059fF775485246999027B3197955',
      },
      {
        poolAddress: '0x2222222222222222222222222222222222222222',
        token0Address: '0x93E66202A11B1772E55407B32B44e5Cd8eda7f22',
        token1Address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
      },
    ];

    expect(filterPoolsForExposure(56, [pools[0]])).toEqual([]);
    expect(filterPoolsForRouting(56, [pools[0]])).toEqual([]);
    expect(filterPoolsForExposure(138, [pools[1]])).toEqual([pools[1]]);
    expect(filterPoolsForRouting(138, [pools[1]])).toEqual([pools[1]]);
  });

  it('marks public cW pools inactive and non-routable until explicitly enabled', () => {
    expect(isPublicPoolActive(56, '0x1111111111111111111111111111111111111111')).toBe(false);
    expect(isPublicPoolRoutable(56, '0x1111111111111111111111111111111111111111')).toBe(false);
  });

  it('routes active c* transport through GRU Transport while keeping WETH on legacy lanes', () => {
    process.env.CHAIN138_L1_BRIDGE = '0x3333333333333333333333333333333333333333';
    process.env.CW_BRIDGE_BSC = '0x4444444444444444444444444444444444444444';
    process.env.CW_RESERVE_VERIFIER_CHAIN138 = '0x5555555555555555555555555555555555555555';
    process.env.CW_STABLECOIN_RESERVE_VAULT = '0x6666666666666666666666666666666666666666';
    process.env.CW_RESERVE_SYSTEM = '0x7777777777777777777777777777777777777777';
    process.env.CW_MAX_OUTSTANDING_USDT_BSC = '1000000';

    const gruRoute = getRouteFromRegistry(138, 56, 'cUSDT');
    expect(gruRoute).not.toBeNull();
    expect(gruRoute?.label).toBe('GRUTransport');
    expect(gruRoute?.bridgeAddress).toBe('0x3333333333333333333333333333333333333333');

    const wethRoute = getRouteFromRegistry(138, 56, 'WETH');
    expect(wethRoute).not.toBeNull();
    expect(wethRoute?.label).toBe('CCIPWETH9Bridge');
  });

  it('reports runtime-ready transport pairs when bridge and reserve refs resolve', () => {
    process.env.CHAIN138_L1_BRIDGE = '0x3333333333333333333333333333333333333333';
    process.env.CW_BRIDGE_BSC = '0x4444444444444444444444444444444444444444';
    process.env.CW_RESERVE_VERIFIER_CHAIN138 = '0x5555555555555555555555555555555555555555';
    process.env.CW_STABLECOIN_RESERVE_VAULT = '0x6666666666666666666666666666666666666666';
    process.env.CW_RESERVE_SYSTEM = '0x7777777777777777777777777777777777777777';
    process.env.CW_MAX_OUTSTANDING_USDT_BSC = '1000000';

    const pairs = getActiveTransportPairs();
    const bscUsdtPair = pairs.find((pair) => pair.key === '138-56-cUSDT-cWUSDT');
    expect(bscUsdtPair?.runtimeBridgeReady).toBe(true);
    expect(bscUsdtPair?.runtimeReserveVerifierReady).toBe(true);
    expect(bscUsdtPair?.runtimeMaxOutstandingReady).toBe(true);
    expect(bscUsdtPair?.runtimeReady).toBe(true);

    const metadata = getGruTransportMetadata();
    expect(metadata?.counts.runtimeReadyTransportPairs).toBeGreaterThan(0);
  });

  it('reports missing runtime requirements when bridge and reserve refs are absent', () => {
    delete process.env.CHAIN138_L1_BRIDGE;
    delete process.env.CW_L1_BRIDGE;
    delete process.env.CW_L1_BRIDGE_CHAIN138;
    delete process.env.CW_BRIDGE_BSC;
    delete process.env.CW_RESERVE_VERIFIER_CHAIN138;
    delete process.env.CW_STABLECOIN_RESERVE_VAULT;
    delete process.env.CW_RESERVE_SYSTEM;
    delete process.env.CW_MAX_OUTSTANDING_USDT_BSC;

    const pairs = getActiveTransportPairs();
    const bscUsdtPair = pairs.find((pair) => pair.key === '138-56-cUSDT-cWUSDT');
    expect(bscUsdtPair?.eligible).toBe(true);
    expect(bscUsdtPair?.runtimeReady).toBe(false);
    expect(bscUsdtPair?.runtimeMissingRequirements).toEqual(
      expect.arrayContaining([
        'bridge:l1Bridge',
        'bridge:l2Bridge',
        'policy:maxOutstanding',
        'reserveVerifier:bridgeRef',
        'reserveVerifier:vaultRef',
        'reserveVerifier:reserveSystemRef',
      ])
    );
  });

  it('evaluates gas-lane supply accounting separately for strict and hybrid backing', () => {
    process.env.CHAIN138_L1_BRIDGE = '0x3333333333333333333333333333333333333333';
    process.env.CW_BRIDGE_MAINNET = '0x4444444444444444444444444444444444444444';
    process.env.CW_GAS_STRICT_ESCROW_VERIFIER_CHAIN138 = '0x5555555555555555555555555555555555555555';
    process.env.CW_GAS_ESCROW_VAULT_CHAIN138 = '0x6666666666666666666666666666666666666666';
    process.env.CW_MAX_OUTSTANDING_ETH_MAINNET_MAINNET = '100';
    process.env.CW_GAS_OUTSTANDING_ETH_MAINNET_MAINNET = '100';
    process.env.CW_GAS_ESCROWED_ETH_MAINNET_MAINNET = '100';
    process.env.CW_GAS_TREASURY_BACKED_ETH_MAINNET_MAINNET = '0';

    process.env.CW_BRIDGE_OPTIMISM = '0x7777777777777777777777777777777777777777';
    process.env.CW_GAS_HYBRID_CAP_VERIFIER_CHAIN138 = '0x8888888888888888888888888888888888888888';
    process.env.CW_GAS_TREASURY_SYSTEM = '0x9999999999999999999999999999999999999999';
    process.env.CW_MAX_OUTSTANDING_ETH_L2_OPTIMISM = '125';
    process.env.CW_GAS_OUTSTANDING_ETH_L2_OPTIMISM = '125';
    process.env.CW_GAS_ESCROWED_ETH_L2_OPTIMISM = '100';
    process.env.CW_GAS_TREASURY_BACKED_ETH_L2_OPTIMISM = '25';
    process.env.CW_GAS_TREASURY_CAP_ETH_L2_OPTIMISM = '25';

    const pairs = getActiveTransportPairs();
    const strictPair = pairs.find((pair) => pair.key === '138-1-cETH-cWETH');
    const hybridPair = pairs.find((pair) => pair.key === '138-10-cETHL2-cWETHL2');

    expect(strictPair?.runtimeSupplyAccountingReady).toBe(true);
    expect(strictPair?.supplyInvariantSatisfied).toBe(true);
    expect(strictPair?.runtimeReady).toBe(true);

    expect(hybridPair?.runtimeSupplyAccountingReady).toBe(true);
    expect(hybridPair?.supplyInvariantSatisfied).toBe(true);
    expect(hybridPair?.runtimeReady).toBe(true);
  });

  it('prefers the active GRU mirrored address even when raw pair ordering is ambiguous', () => {
    const loader = require(path.join(process.cwd(), '../../..', 'config', 'token-mapping-loader.cjs')) as {
      getMappedAddress: (fromChainId: number, toChainId: number, tokenAddressOnSource: string, jsonPath?: string) => string | undefined;
    };
    const fixturePath = path.join(os.tmpdir(), `gru-transport-mapping-${Date.now()}.json`);
    const canonicalAddress = '0x93E66202A11B1772E55407B32B44e5Cd8eda7f22';
    const mirroredAddress = '0x9a1D0dBEE997929ED02fD19E0E199704d20914dB';
    const nativeAddress = '0x55d398326f99059fF775485246999027B3197955';

    fs.writeFileSync(
      fixturePath,
      JSON.stringify(
        {
          pairs: [
            {
              fromChainId: 138,
              toChainId: 56,
              tokens: [
                {
                  key: 'Compliant_USDT_cW',
                  name: 'cUSDT->cWUSDT',
                  addressFrom: canonicalAddress,
                  addressTo: mirroredAddress,
                },
                {
                  key: 'Compliant_USDT',
                  name: 'cUSDT',
                  addressFrom: canonicalAddress,
                  addressTo: nativeAddress,
                },
              ],
            },
          ],
        },
        null,
        2
      )
    );

    try {
      expect(loader.getMappedAddress(138, 56, canonicalAddress, fixturePath)).toBe(mirroredAddress);
    } finally {
      fs.unlinkSync(fixturePath);
    }
  });

  it('does not fall back to legacy stable bridges for active GRU assets when peer bridges are missing', () => {
    delete process.env.CHAIN138_L1_BRIDGE;
    delete process.env.CW_L1_BRIDGE;
    delete process.env.CW_L1_BRIDGE_CHAIN138;
    delete process.env.CW_BRIDGE_BSC;
    process.env.CCIP_STABLE_BRIDGE_CHAIN138 = '0x5555555555555555555555555555555555555555';
    process.env.UNIVERSAL_CCIP_BRIDGE_ADDRESS = '0x6666666666666666666666666666666666666666';

    jest.resetModules();
    const { getRouteFromRegistry: getFreshRouteFromRegistry } = require('./cross-chain-bridges') as typeof import('./cross-chain-bridges');

    expect(getFreshRouteFromRegistry(138, 56, 'cUSDT')).toBeNull();
  });

  it('resolves the token mapping loader from an explicit deployed-layout env override', () => {
    const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'token-mapping-loader-'));
    const configDir = path.join(tempRoot, 'config');
    fs.mkdirSync(configDir, { recursive: true });
    const loaderPath = path.join(configDir, 'token-mapping-loader.cjs');

    fs.writeFileSync(
      loaderPath,
      'module.exports = { getGruTransportMetadata: () => ({ system: { shortName: "Test" }, counts: { transportPairs: 0 } }), getActiveTransportPairs: () => [] };'
    );

    process.env.TOKEN_MAPPING_LOADER_PATH = loaderPath;

    try {
      expect(resolveTokenMappingLoaderPath()).toBe(loaderPath);
      expect(getGruTransportMetadata()?.system?.shortName).toBe('Test');
      expect(getActiveTransportPairs()).toEqual([]);
    } finally {
      fs.rmSync(tempRoot, { recursive: true, force: true });
    }
  });
});
