import { createServer } from 'http';
import express from 'express';
import fs from 'fs/promises';
import path from 'path';
import bridgeRoutes from './bridge';

jest.mock('../middleware/cache');

function createApp() {
  const app = express();
  app.use('/api/v1/bridge', bridgeRoutes);
  return app;
}

async function startServer(app: express.Application): Promise<{ server: ReturnType<typeof createServer>; baseUrl: string }> {
  const server = createServer(app);
  await new Promise<void>((resolve) => server.listen(0, () => resolve()));
  const port = (server.address() as { port: number }).port;
  return { server, baseUrl: `http://127.0.0.1:${port}` };
}

describe('Bridge API GRU Transport status', () => {
  let server: ReturnType<typeof createServer>;
  let baseUrl: string;
  const originalChain138Bridge = process.env.CHAIN138_L1_BRIDGE;
  const originalBscBridge = process.env.CW_BRIDGE_BSC;
  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 originalBridgeListPath = process.env.BRIDGE_LIST_JSON_PATH;
  const originalBridgeListUrl = process.env.BRIDGE_LIST_JSON_URL;
  const originalCwL1Bridge = process.env.CW_L1_BRIDGE;
  const originalCwL1BridgeChain138 = process.env.CW_L1_BRIDGE_CHAIN138;

  beforeAll(async () => {
    const started = await startServer(createApp());
    server = started.server;
    baseUrl = started.baseUrl;
  });

  afterEach(() => {
    if (originalChain138Bridge === undefined) {
      delete process.env.CHAIN138_L1_BRIDGE;
    } else {
      process.env.CHAIN138_L1_BRIDGE = originalChain138Bridge;
    }
    if (originalBscBridge === undefined) {
      delete process.env.CW_BRIDGE_BSC;
    } else {
      process.env.CW_BRIDGE_BSC = originalBscBridge;
    }
    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;
    }
    if (originalBridgeListPath === undefined) {
      delete process.env.BRIDGE_LIST_JSON_PATH;
    } else {
      process.env.BRIDGE_LIST_JSON_PATH = originalBridgeListPath;
    }
    if (originalBridgeListUrl === undefined) {
      delete process.env.BRIDGE_LIST_JSON_URL;
    } else {
      process.env.BRIDGE_LIST_JSON_URL = originalBridgeListUrl;
    }
    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;
    }
  });

  afterAll((done) => {
    server.close(done);
  });

  it('returns GRU transport pair runtime readiness on /status', async () => {
    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 res = await fetch(`${baseUrl}/api/v1/bridge/status`);
    expect(res.status).toBe(200);
    const body = (await res.json()) as Record<string, any>;
    expect(body.gruTransport?.summary?.runtimeReadyTransportPairs).toEqual(expect.any(Number));
    expect(body.gruTransport?.pairs).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          key: '138-56-cUSDT-cWUSDT',
          runtimeReady: true,
        }),
        expect.objectContaining({
          key: '138-10-cETHL2-cWETHL2',
          assetClass: 'gas_native',
          familyKey: 'eth_l2',
          backingMode: 'hybrid_cap',
        }),
      ])
    );
    expect(body.gruTransport?.gasAssetFamilies).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          familyKey: 'eth_mainnet',
          backingMode: 'strict_escrow',
        }),
      ])
    );
  });

  it('returns GRU transport summary counts on /metrics', async () => {
    const res = await fetch(`${baseUrl}/api/v1/bridge/metrics`);
    expect(res.status).toBe(200);
    const body = (await res.json()) as Record<string, any>;
    expect(body.gruTransport?.summary).toMatchObject({
      transportPairs: expect.any(Number),
      runtimeReadyTransportPairs: expect.any(Number),
    });
  });

  it('returns blocked pairs with missing requirements on /preflight', async () => {
    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 res = await fetch(`${baseUrl}/api/v1/bridge/preflight`);
    expect(res.status).toBe(200);
    const body = (await res.json()) as Record<string, any>;
    expect(body.ok).toBe(false);
    expect(body.gruTransport?.blockedPairs).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          key: '138-56-cUSDT-cWUSDT',
          runtimeReady: false,
          runtimeMissingRequirements: expect.arrayContaining([
            'bridge:l1Bridge',
            'bridge:l2Bridge',
          ]),
        }),
        expect.objectContaining({
          key: '138-10-cETHL2-cWETHL2',
          backingMode: 'hybrid_cap',
          runtimeMissingRequirements: expect.arrayContaining([
            'supplyAccounting:outstanding',
            'supplyAccounting:treasuryCap',
          ]),
        }),
      ])
    );
  });

  it('reads runtime bridge routes from a local json file when configured', async () => {
    const tempPath = path.join('/tmp', `bridge-routes-${Date.now()}.json`);
    await fs.writeFile(
      tempPath,
      JSON.stringify({
        routes: {
          weth9: {
            'Dynamic Ethereum Mainnet (1)': '0x9999999999999999999999999999999999999999',
          },
          weth10: {},
        },
        chain138Bridges: {
          weth9: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
          weth10: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
        },
      })
    );
    process.env.BRIDGE_LIST_JSON_PATH = tempPath;

    try {
      const res = await fetch(`${baseUrl}/api/v1/bridge/routes`);
      expect(res.status).toBe(200);
      const body = (await res.json()) as Record<string, any>;
      expect(body.source).toBe('runtime-file');
      expect(body.routes?.weth9).toMatchObject({
        'Dynamic Ethereum Mainnet (1)': '0x9999999999999999999999999999999999999999',
      });
      expect(body.chain138Bridges?.weth9).toBe('0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
    } finally {
      await fs.unlink(tempPath).catch(() => undefined);
    }
  });

  it('falls back to a runtime bridge routes file when BRIDGE_LIST_JSON_URL fails', async () => {
    const tempPath = path.join('/tmp', `bridge-routes-fallback-${Date.now()}.json`);
    await fs.writeFile(
      tempPath,
      JSON.stringify({
        routes: {
          weth9: {
            'Fallback Ethereum Mainnet (1)': '0x1234512345123451234512345123451234512345',
          },
          weth10: {},
        },
        chain138Bridges: {
          weth9: '0xcccccccccccccccccccccccccccccccccccccccc',
          weth10: '0xdddddddddddddddddddddddddddddddddddddddd',
        },
      })
    );
    process.env.BRIDGE_LIST_JSON_URL = 'http://127.0.0.1:1/bridge-routes.json';
    process.env.BRIDGE_LIST_JSON_PATH = tempPath;

    try {
      const res = await fetch(`${baseUrl}/api/v1/bridge/routes`);
      expect(res.status).toBe(200);
      const body = (await res.json()) as Record<string, any>;
      expect(body.source).toBe('runtime-file');
      expect(body.routes?.weth9).toMatchObject({
        'Fallback Ethereum Mainnet (1)': '0x1234512345123451234512345123451234512345',
      });
      expect(body.chain138Bridges?.weth9).toBe('0xcccccccccccccccccccccccccccccccccccccccc');
    } finally {
      await fs.unlink(tempPath).catch(() => undefined);
    }
  });
});

describe('Bridge API relay lane messages', () => {
  let server: ReturnType<typeof createServer>;
  let baseUrl: string;

  beforeAll(async () => {
    const started = await startServer(createApp());
    server = started.server;
    baseUrl = started.baseUrl;
  });

  afterAll(async () => {
    await new Promise<void>((resolve, reject) => {
      server.close((err) => (err ? reject(err) : resolve()));
    });
  });

  it('returns 138-monad relay messages with seed canaries when skipRpc=1', async () => {
    const res = await fetch(`${baseUrl}/api/v1/bridge/messages?lane=138-monad&skipRpc=1`);
    expect(res.status).toBe(200);
    const body = (await res.json()) as Record<string, unknown>;
    expect(body.ok).toBe(true);
    expect(body.laneId).toBe('138-monad-relay');
    expect(body.integration).toMatchObject({ ccipExplorer: false, creRequired: false });
    const messages = body.messages as Array<{ messageId: string; status: string }>;
    expect(Array.isArray(messages)).toBe(true);
    expect(messages.length).toBeGreaterThanOrEqual(2);
    expect(messages.some((m) => m.status === 'delivered')).toBe(true);
  });

  it('rejects unknown lane', async () => {
    const res = await fetch(`${baseUrl}/api/v1/bridge/messages?lane=unknown`);
    expect(res.status).toBe(400);
  });
});
