import { createServer } from 'http';
import express from 'express';
let plannerV2Routes: any;
const mockPlan = jest.fn();
const mockCapabilities = jest.fn();
const mockBuild = jest.fn();

jest.mock('../../services/best-execution-planner', () => ({
  __esModule: true,
  BestExecutionPlanner: jest.fn().mockImplementation(() => ({
    plan: mockPlan,
    getCapabilities: mockCapabilities,
  })),
}));

jest.mock('../../services/internal-execution-plan-v2', () => ({
  __esModule: true,
  InternalExecutionPlanV2Builder: jest.fn().mockImplementation(() => ({
    build: mockBuild,
  })),
}));

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

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

  beforeAll(async () => {
    plannerV2Routes = require('./planner-v2').default;
    const started = await startServer(createApp());
    server = started.server;
    baseUrl = started.baseUrl;
  });

  beforeEach(() => {
    mockPlan.mockReset();
    mockCapabilities.mockReset();
    mockBuild.mockReset();
  });

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

  it('returns provider capabilities', async () => {
    mockCapabilities.mockReturnValue([{ provider: 'dodo', live: true }]);

    const res = await fetch(`${baseUrl}/api/v2/providers/capabilities?chainId=138`);
    expect(res.status).toBe(200);
    const body = (await res.json()) as Record<string, any>;
    expect(body.providers).toEqual([{ provider: 'dodo', live: true }]);
  });

  it('returns a planner response for one-chain route planning', async () => {
    mockPlan.mockResolvedValue({
      planId: 'test-plan',
      decision: 'direct-pool',
      estimatedAmountOut: '1000',
      minAmountOut: '990',
      estimatedGasUsd: 0.22,
      legs: [],
      alternatives: [],
      confidenceScore: 0.9,
      riskFlags: [],
      selectedRouteReason: 'selected',
      rejectedAlternatives: [],
      staleness: { maxFreshnessSeconds: 12, hasStaleLeg: false },
      generatedAt: new Date().toISOString(),
      sourceChainId: 138,
      destinationChainId: 138,
      tokenIn: '0x1',
      tokenOut: '0x2',
    });

    const res = await fetch(`${baseUrl}/api/v2/routes/plan`, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        sourceChainId: 138,
        tokenIn: '0x1',
        tokenOut: '0x2',
        amountIn: '1000',
      }),
    });
    expect(res.status).toBe(200);
    const body = (await res.json()) as Record<string, any>;
    expect(body.planId).toBe('test-plan');
    expect(mockPlan).toHaveBeenCalled();
  });

  it('returns an internal execution plan when the planner can encode calldata', async () => {
    mockBuild.mockResolvedValue({
      generatedAt: new Date().toISOString(),
      plannerResponse: {
        planId: 'test-plan',
      },
      execution: {
        kind: 'route',
        contractAddress: '0xrouter',
        functionName: 'executeRoute',
        signature: 'executeRoute((...))',
        args: [],
        encodedCalldata: '0xdeadbeef',
      },
    });

    const res = await fetch(`${baseUrl}/api/v2/routes/internal-execution-plan`, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        sourceChainId: 138,
        tokenIn: '0x1',
        tokenOut: '0x2',
        amountIn: '1000',
      }),
    });
    expect(res.status).toBe(200);
    const body = (await res.json()) as Record<string, any>;
    expect(body.execution.encodedCalldata).toBe('0xdeadbeef');
  });

  it('returns a JSON 500 instead of crashing when internal plan generation rejects', async () => {
    mockBuild.mockRejectedValue(new Error('planner exploded'));

    const res = await fetch(`${baseUrl}/api/v2/routes/internal-execution-plan`, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        sourceChainId: 138,
        tokenIn: '0x1',
        tokenOut: '0x2',
        amountIn: '1000',
      }),
    });

    expect(res.status).toBe(500);
    await expect(res.json()).resolves.toEqual({ error: 'Internal server error' });
  });
});
