import { createServer } from 'http';
import express from 'express';
import fs from 'fs/promises';
import path from 'path';
import configRoutes from './config';

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

function createApp() {
  const app = express();
  app.use('/api/v1', configRoutes);
  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('Config API runtime networks loader', () => {
  let server: ReturnType<typeof createServer>;
  let baseUrl: string;
  const originalNetworksPath = process.env.NETWORKS_JSON_PATH;
  const originalNetworksUrl = process.env.NETWORKS_JSON_URL;

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

  afterEach(() => {
    if (originalNetworksPath === undefined) {
      delete process.env.NETWORKS_JSON_PATH;
    } else {
      process.env.NETWORKS_JSON_PATH = originalNetworksPath;
    }
    if (originalNetworksUrl === undefined) {
      delete process.env.NETWORKS_JSON_URL;
    } else {
      process.env.NETWORKS_JSON_URL = originalNetworksUrl;
    }
  });

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

  it('serves networks from a runtime file when configured', async () => {
    const tempPath = path.join('/tmp', `token-aggregation-networks-${Date.now()}.json`);
    await fs.writeFile(
      tempPath,
      JSON.stringify({
        version: { major: 9, minor: 9, patch: 9 },
        chains: [
          {
            chainId: '0x8a',
            chainIdDecimal: 138,
            chainName: 'Dynamic Chain 138',
            rpcUrls: ['https://dynamic-rpc.example'],
            nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
            blockExplorerUrls: ['https://dynamic-explorer.example'],
            oracles: [{ name: 'ETH/USD', address: '0x1111111111111111111111111111111111111111' }],
          },
        ],
      })
    );
    process.env.NETWORKS_JSON_PATH = tempPath;

    try {
      const res = await fetch(`${baseUrl}/api/v1/networks`);
      expect(res.status).toBe(200);
      const body = (await res.json()) as Record<string, any>;
      expect(body.source).toBe('runtime-file');
      expect(body.version).toBe('9.9.9');
      expect(body.networks).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            chainName: 'Dynamic Chain 138',
          }),
        ])
      );

      const cfgRes = await fetch(`${baseUrl}/api/v1/config?chainId=138`);
      expect(cfgRes.status).toBe(200);
      const cfgBody = (await cfgRes.json()) as Record<string, any>;
      expect(cfgBody.source).toBe('runtime-file');
      expect(cfgBody.oracles).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            name: 'ETH/USD',
          }),
        ])
      );
    } finally {
      await fs.unlink(tempPath).catch(() => undefined);
    }
  });

  it('uses the remote networks source for config when NETWORKS_JSON_URL is set', async () => {
    const remotePayload = {
      version: '7.7.7',
      networks: [
        {
          chainId: '0x8a',
          chainIdDecimal: 138,
          chainName: 'Remote Chain 138',
          rpcUrls: ['https://remote-rpc.example'],
          nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
          blockExplorerUrls: ['https://remote-explorer.example'],
          oracles: [{ name: 'BTC/USD', address: '0x2222222222222222222222222222222222222222' }],
        },
      ],
    };
    const remoteServer = createServer((_req, res) => {
      res.setHeader('Content-Type', 'application/json');
      res.end(JSON.stringify(remotePayload));
    });
    await new Promise<void>((resolve) => remoteServer.listen(0, () => resolve()));
    const remotePort = (remoteServer.address() as { port: number }).port;
    process.env.NETWORKS_JSON_URL = `http://127.0.0.1:${remotePort}/networks.json`;

    try {
      const res = await fetch(`${baseUrl}/api/v1/config?chainId=138`);
      expect(res.status).toBe(200);
      const body = (await res.json()) as Record<string, any>;
      expect(body.source).toBe('remote-url');
      expect(body.version).toBe('7.7.7');
      expect(body.oracles).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            name: 'BTC/USD',
          }),
        ])
      );
    } finally {
      await new Promise<void>((resolve, reject) => remoteServer.close((err) => (err ? reject(err) : resolve())));
    }
  });
});
