import { createServer } from 'http';
import express from 'express';
import { getCanonicalTokenBySymbol } from '../../config/canonical-tokens';

const mockGetTokens = jest.fn();
const mockGetToken = jest.fn();
const mockSearchTokens = jest.fn();
const mockGetMarketData = jest.fn();
const mockGetPoolsByToken = jest.fn();
const mockGetPool = jest.fn();
const mockGetLiveDodoPools = jest.fn();
const mockResolveTokenDisplay = jest.fn();
const mockResolvePoolTokenDisplays = jest.fn();
const mockGetTokenByContract = jest.fn();

jest.mock('../../database/repositories/token-repo', () => ({
  TokenRepository: jest.fn().mockImplementation(() => ({
    getTokens: mockGetTokens,
    getToken: mockGetToken,
    searchTokens: mockSearchTokens,
  })),
}));

jest.mock('../../database/repositories/market-data-repo', () => ({
  MarketDataRepository: jest.fn().mockImplementation(() => ({
    getMarketData: mockGetMarketData,
  })),
}));

jest.mock('../../database/repositories/pool-repo', () => ({
  PoolRepository: jest.fn().mockImplementation(() => ({
    getPoolsByToken: mockGetPoolsByToken,
    getPool: mockGetPool,
  })),
}));

jest.mock('../../indexer/ohlcv-generator', () => ({
  OHLCVGenerator: jest.fn().mockImplementation(() => ({
    getOHLCV: jest.fn().mockResolvedValue([]),
  })),
}));

const mockGetMarketDataAdapter = jest.fn();

jest.mock('../../adapters/coingecko-adapter', () => ({
  CoinGeckoAdapter: jest.fn().mockImplementation(() => ({
    getTokenByContract: mockGetTokenByContract,
    getMarketData: mockGetMarketDataAdapter,
    getTrending: jest.fn().mockResolvedValue([]),
  })),
}));

jest.mock('../../adapters/cmc-adapter', () => ({
  CoinMarketCapAdapter: jest.fn().mockImplementation(() => ({
    getTokenByContract: mockGetTokenByContract,
    getMarketData: mockGetMarketDataAdapter,
  })),
}));

jest.mock('../../adapters/dexscreener-adapter', () => ({
  DexScreenerAdapter: jest.fn().mockImplementation(() => ({
    getTokenByContract: mockGetTokenByContract,
    getMarketData: mockGetMarketDataAdapter,
  })),
}));

jest.mock('../../services/live-dodo-fallback', () => ({
  getLiveDodoPools: (...args: unknown[]) => mockGetLiveDodoPools(...args),
}));

jest.mock('../../services/token-display', () => ({
  resolveTokenDisplay: (...args: unknown[]) => mockResolveTokenDisplay(...args),
  resolvePoolTokenDisplays: (...args: unknown[]) => mockResolvePoolTokenDisplays(...args),
}));

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

const tokensRoutes = require('./tokens').default as typeof import('./tokens').default;

function createApp() {
  const app = express();
  app.use('/api/v1', tokensRoutes);
  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('Tokens API', () => {
  let server: ReturnType<typeof createServer>;
  let baseUrl: string;

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

  beforeEach(() => {
    jest.clearAllMocks();
    mockGetMarketDataAdapter.mockResolvedValue(null);
    mockGetTokens.mockResolvedValue([]);
    mockGetToken.mockResolvedValue(null);
    mockSearchTokens.mockResolvedValue([]);
    mockGetMarketData.mockResolvedValue(null);
    mockGetPoolsByToken.mockResolvedValue([]);
    mockGetPool.mockResolvedValue(null);
    mockGetLiveDodoPools.mockResolvedValue([]);
    mockResolveTokenDisplay.mockResolvedValue({
      address: '',
      name: 'Unknown Token',
      symbol: 'UNKNOWN',
      decimals: 18,
      source: 'fallback',
    });
    mockResolvePoolTokenDisplays.mockResolvedValue({
      token0: { address: '', symbol: 'UNKNOWN', name: 'Unknown Token', source: 'fallback' },
      token1: { address: '', symbol: 'UNKNOWN', name: 'Unknown Token', source: 'fallback' },
    });
    mockGetTokenByContract.mockResolvedValue(null);
  });

  afterAll(async () => {
    await new Promise<void>((resolve, reject) => {
      server.close((error) => {
        if (error) {
          reject(error);
          return;
        }
        resolve();
      });
    });
  });

  it('lists canonical 138 tokens with stable and ETH-family fallback pricing when db market data is missing', async () => {
    const usdt = getCanonicalTokenBySymbol(138, 'USDT');
    const weth = getCanonicalTokenBySymbol(138, 'WETH');
    const weth10 = getCanonicalTokenBySymbol(138, 'WETH10');

    expect(usdt?.addresses[138]).toBeTruthy();
    expect(weth?.addresses[138]).toBeTruthy();
    expect(weth10?.addresses[138]).toBeTruthy();

    const res = await fetch(`${baseUrl}/api/v1/tokens?chainId=138&limit=400`);
    expect(res.status).toBe(200);

    const body = (await res.json()) as Record<string, any>;
    expect(body.source).toBe('canonical');

    const findByAddress = (address?: string) =>
      body.tokens.find((token: Record<string, any>) => token.address === address?.toLowerCase());

    expect(findByAddress(usdt?.addresses[138])).toMatchObject({
      symbol: 'USDT',
      decimals: 6,
      market: expect.objectContaining({
        priceUsd: 1,
        volume24h: 0,
        liquidityUsd: 0,
      }),
    });

    expect(findByAddress(weth?.addresses[138])).toMatchObject({
      symbol: 'WETH',
      decimals: 18,
      market: expect.objectContaining({
        priceUsd: 2490,
      }),
    });

    expect(findByAddress(weth10?.addresses[138])).toMatchObject({
      symbol: 'WETH10',
      decimals: 18,
      market: expect.objectContaining({
        priceUsd: 2490,
      }),
    });
  });

  it('fills missing priceUsd on token detail responses while preserving repository market fields', async () => {
    const weth10 = getCanonicalTokenBySymbol(138, 'WETH10');
    expect(weth10?.addresses[138]).toBeTruthy();
    const weth10Address = String(weth10?.addresses[138]).toLowerCase();

    mockGetMarketData.mockResolvedValue({
      chainId: 138,
      tokenAddress: weth10Address,
      priceUsd: undefined,
      volume24h: 1234,
      volume7d: 5678,
      volume30d: 9012,
      liquidityUsd: 3456,
      holdersCount: 78,
      transfers24h: 9,
      lastUpdated: new Date('2026-04-16T00:00:00.000Z'),
    });

    const res = await fetch(`${baseUrl}/api/v1/tokens/${weth10Address}?chainId=138`);
    expect(res.status).toBe(200);

    const body = (await res.json()) as Record<string, any>;
    expect(body.token).toMatchObject({
      symbol: 'WETH10',
      decimals: 18,
      market: expect.objectContaining({
        priceUsd: 2490,
        volume24h: 1234,
        liquidityUsd: 3456,
      }),
      hasDodoPool: false,
    });
    expect(body.token.canonicalLiquidity).toBeUndefined();
  });
});
