import { BestExecutionPlanner } from './best-execution-planner';
import { PlannerMetricsRepository } from '../database/repositories/planner-metrics-repo';
import { RouteGraphBuilder } from './route-graph-builder';
import type { BridgeRouteCandidate, PlannerResponse, SwapGraphEdge } from './planner-v2-types';

const mockDodoV3Quote = jest.fn();

jest.mock('./dodo-v3-pilot', () => ({
  __esModule: true,
  quoteChain138DodoV3AmountOut: (...args: unknown[]) => mockDodoV3Quote(...args),
  encodeChain138DodoV3ProviderData: (poolAddress: string) =>
    `0x000000000000000000000000${String(poolAddress).replace(/^0x/, '').toLowerCase()}`,
  isChain138DodoV3ExecutionLive: () => true,
}));

class MockPlannerMetricsRepository {
  async getCachedPlan(): Promise<PlannerResponse | null> {
    return null;
  }
  async cachePlan(): Promise<void> {}
  async recordProviderSnapshots(): Promise<void> {}
  async recordPlannedRouteMetrics(): Promise<void> {}
}

class MockGraphBuilder {
  private swapEdges: SwapGraphEdge[];
  private bridgeCandidates: BridgeRouteCandidate[];

  constructor(swapEdges: SwapGraphEdge[] = [], bridgeCandidates: BridgeRouteCandidate[] = []) {
    this.swapEdges = swapEdges;
    this.bridgeCandidates = bridgeCandidates;
  }

  async buildSwapEdges(): Promise<SwapGraphEdge[]> {
    return this.swapEdges;
  }

  buildBridgeCandidates(): BridgeRouteCandidate[] {
    return this.bridgeCandidates;
  }
}

const WETH = '0xc02aaA39b223fe8d0a0e5c4f27ead9083c756cc2'.toLowerCase();
const USDT = '0x004b63a7b5b0e06f6bb6adb4a5f9f590bf3182d1'.toLowerCase();
const USDC = '0x71d6687f38b93ccad569fa6352c876eea967201b'.toLowerCase();
const CUSDT = '0x93e66202a11b1772e55407b32b44e5cd8eda7f22'.toLowerCase();
const CEURT = '0xdf4b71c61e5912712c1bdd451416b9ac26949d72'.toLowerCase();
const CXAUC = '0x290e52a8819a4fbd0714e517225429aa2b70ec6b'.toLowerCase();
const WETH10 = '0xf4bb2e28688e89fcce3c0580d37d36a7672e8a9f'.toLowerCase();

function swapEdge(
  tokenInAddress: string,
  tokenOutAddress: string,
  reserveIn: string,
  reserveOut: string,
  tokenInSymbol: string,
  tokenOutSymbol: string,
  provider: SwapGraphEdge['provider'] = 'dodo'
): SwapGraphEdge {
  return {
    kind: 'swap',
    provider,
    chainId: 138,
    tokenInAddress,
    tokenOutAddress,
    tokenInSymbol,
    tokenOutSymbol,
    reserveIn,
    reserveOut,
    target: '0x3f729632e9553ebaccde2e9b4c8f2b285b014f2e',
    poolAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
    providerData: { poolAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' },
    providerDataHex: '0x000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
    totalLiquidityUsd: 2_000_000,
    freshnessSeconds: 60,
    notes: [],
  };
}

describe('BestExecutionPlanner', () => {
  beforeEach(() => {
    mockDodoV3Quote.mockReset();
  });

  it('prefers the direct route when it scores better than multi-hop', async () => {
    const graphBuilder = new MockGraphBuilder([
      swapEdge(WETH, USDT, '100000000000000000000', '2000000000000', 'WETH', 'USDT'),
      swapEdge(WETH, CUSDT, '100000000000000000000', '1900000000000', 'WETH', 'cUSDT'),
      swapEdge(CUSDT, USDT, '1900000000000', '1850000000000', 'cUSDT', 'USDT'),
    ]) as unknown as RouteGraphBuilder;

    const planner = new BestExecutionPlanner(graphBuilder, new MockPlannerMetricsRepository() as unknown as PlannerMetricsRepository);
    const response = await planner.plan({
      sourceChainId: 138,
      tokenIn: WETH,
      tokenOut: USDT,
      amountIn: '1000000000000000000',
    });

    expect(response.decision).toBe('direct-pool');
    expect(response.legs).toHaveLength(1);
    expect(response.legs[0].tokenOutAddress).toBe(USDT);
  });

  it('chooses multi-hop when no direct route exists', async () => {
    const graphBuilder = new MockGraphBuilder([
      swapEdge(WETH, CUSDT, '100000000000000000000', '1900000000000', 'WETH', 'cUSDT'),
      swapEdge(CUSDT, USDC, '1900000000000', '1880000000000', 'cUSDT', 'USDC'),
    ]) as unknown as RouteGraphBuilder;

    const planner = new BestExecutionPlanner(graphBuilder, new MockPlannerMetricsRepository() as unknown as PlannerMetricsRepository);
    const response = await planner.plan({
      sourceChainId: 138,
      tokenIn: WETH,
      tokenOut: USDC,
      amountIn: '1000000000000000000',
    });

    expect(response.decision).toBe('multi-hop');
    expect(response.legs).toHaveLength(2);
    expect(response.legs[0].tokenOutAddress).toBe(CUSDT);
    expect(response.legs[1].tokenOutAddress).toBe(USDC);
  });

  it('blocks commodity intermediates under institutional policy unless explicitly enabled', async () => {
    const graphBuilder = new MockGraphBuilder([
      swapEdge(CEURT, CXAUC, '1000000000000', '1000000000000', 'cEURT', 'cXAUC'),
      swapEdge(CXAUC, CUSDT, '1000000000000', '1000000000000', 'cXAUC', 'cUSDT'),
    ]) as unknown as RouteGraphBuilder;

    const planner = new BestExecutionPlanner(graphBuilder, new MockPlannerMetricsRepository() as unknown as PlannerMetricsRepository);
    const response = await planner.plan({
      sourceChainId: 138,
      tokenIn: CEURT,
      tokenOut: CUSDT,
      amountIn: '1000000',
      constraints: {
        complianceProfile: 'institutional',
      },
    });

    expect(response.decision).toBe('unresolved');
    expect(response.riskFlags).toContain('no-route');
  });

  it('returns deterministic plan ids for identical requests', async () => {
    const graphBuilder = new MockGraphBuilder([
      swapEdge(WETH, USDT, '100000000000000000000', '2000000000000', 'WETH', 'USDT'),
    ]) as unknown as RouteGraphBuilder;

    const planner = new BestExecutionPlanner(graphBuilder, new MockPlannerMetricsRepository() as unknown as PlannerMetricsRepository);
    const request = {
      sourceChainId: 138,
      tokenIn: WETH,
      tokenOut: USDT,
      amountIn: '1000000000000000000',
    };

    const first = await planner.plan(request);
    const second = await planner.plan(request);

    expect(first.planId).toBe(second.planId);
    expect(first.legs).toEqual(second.legs);
  });

  it('returns a live planner route and executable router-v2 calldata for the DODO v3 pilot when execution is enabled', async () => {
    mockDodoV3Quote.mockResolvedValue(211660490n);

    const graphBuilder = new MockGraphBuilder([
      swapEdge(WETH10, USDT, '2010000000000000000', '4978833460', 'WETH10', 'USDT', 'dodo_v3'),
    ]) as unknown as RouteGraphBuilder;

    const planner = new BestExecutionPlanner(graphBuilder, new MockPlannerMetricsRepository() as unknown as PlannerMetricsRepository);
    const response = await planner.plan({
      sourceChainId: 138,
      tokenIn: WETH10,
      tokenOut: USDT,
      amountIn: '100000000000000000',
    });

    expect(response.decision).toBe('direct-pool');
    expect(response.legs).toHaveLength(1);
    expect(response.legs[0].provider).toBe('dodo_v3');
    expect(response.estimatedAmountOut).toBe('211660490');
    expect(response.routePlan).toBeDefined();
    expect(response.routePlan?.legs[0]?.provider).toBe(8);
    expect(response.riskFlags).toContain('pilot-venue');
    expect(response.riskFlags).not.toContain('manual-execution-only');
  });
});
