import { LiveAggregatorRoute, loadAggregatorRouteMatrix } from '../config/aggregator-route-matrix';

export interface InternalExecutionPlanRequest {
  routeId?: string;
  fromChainId?: number;
  toChainId?: number;
  tokenIn?: string;
  tokenOut?: string;
  amountIn: string;
  slippageBps?: string;
}

export interface InternalExecutionStep {
  kind: 'approve' | 'swap';
  description: string;
  tokenAddress?: string;
  spender?: string;
  contractAddress?: string;
  functionName?: string;
  signature?: string;
  args?: Array<string | number | Record<string, string>>;
  amountSource?: 'user_input' | 'previous_step_output';
  estimatedAmountOut?: string;
  minAmountOut?: string;
}

export interface InternalExecutionPlan {
  routeId: string;
  chainId: number;
  executor: {
    protocol: 'dodo_pmm';
    contractAddress: string;
  };
  amountIn: string;
  slippageBps: string;
  notes: string[];
  steps: InternalExecutionStep[];
}

function quoteAmountOut(amountIn: bigint, reserveIn: bigint, reserveOut: bigint): bigint {
  if (reserveIn === 0n) return 0n;
  const amountInWithFee = amountIn * 997n;
  return (reserveOut * amountInWithFee) / (reserveIn * 1000n + amountInWithFee);
}

function normalizeAddress(address?: string): string | undefined {
  return address?.trim().toLowerCase() || undefined;
}

function findRoute(request: InternalExecutionPlanRequest, routes: LiveAggregatorRoute[]): LiveAggregatorRoute | undefined {
  if (request.routeId) {
    return routes.find((route) => route.routeId === request.routeId);
  }

  const tokenIn = normalizeAddress(request.tokenIn);
  const tokenOut = normalizeAddress(request.tokenOut);

  const matches = routes.filter((route) => {
    if (request.fromChainId && route.fromChainId !== request.fromChainId) return false;
    if (request.toChainId && route.toChainId !== request.toChainId) return false;
    if (tokenIn && normalizeAddress(route.tokenInAddress) !== tokenIn) return false;
    if (tokenOut && normalizeAddress(route.tokenOutAddress) !== tokenOut) return false;
    return route.routeType === 'swap';
  });

  return matches.length === 1 ? matches[0] : undefined;
}

function buildPoolReserveMap(routes: LiveAggregatorRoute[]): Map<string, Record<string, string>> {
  const map = new Map<string, Record<string, string>>();
  for (const route of routes) {
    for (const leg of route.legs || []) {
      if (leg.poolAddress && leg.reserves) {
        map.set(leg.poolAddress.toLowerCase(), leg.reserves);
      }
    }
  }
  return map;
}

function estimateLegOut(
  amountIn: bigint,
  leg: NonNullable<LiveAggregatorRoute['legs']>[number],
  reserveMap: Map<string, Record<string, string>>
): string | undefined {
  if (!leg.poolAddress || !leg.tokenInAddress || !leg.tokenOutAddress) return undefined;
  const reserves = reserveMap.get(leg.poolAddress.toLowerCase());
  if (!reserves) return undefined;
  const tokenInAddress = leg.tokenInAddress.toLowerCase();
  const tokenOutAddress = leg.tokenOutAddress.toLowerCase();

  const entries = Object.entries(reserves);
  const reserveInEntry = entries.find(([_, value], idx) => {
    const key = entries[idx][0];
    return key.toLowerCase().includes('cusdt') && tokenInAddress === '0x93e66202a11b1772e55407b32b44e5cd8eda7f22'
      || key.toLowerCase().includes('cusdc') && tokenInAddress === '0xf22258f57794cc8e06237084b353ab30fffa640b'
      || key.toLowerCase().includes('usdt') && tokenInAddress === '0x004b63a7b5b0e06f6bb6adb4a5f9f590bf3182d1'
      || key.toLowerCase().includes('usdc') && tokenInAddress === '0x71d6687f38b93ccad569fa6352c876eea967201b'
      || key.toLowerCase().includes('ceurt') && tokenInAddress === '0xdf4b71c61e5912712c1bdd451416b9ac26949d72'
      || key.toLowerCase().includes('cxauc') && tokenInAddress === '0x290e52a8819a4fbd0714e517225429aa2b70ec6b';
  });
  const reserveOutEntry = entries.find(([_, value], idx) => {
    const key = entries[idx][0];
    return key.toLowerCase().includes('cusdt') && tokenOutAddress === '0x93e66202a11b1772e55407b32b44e5cd8eda7f22'
      || key.toLowerCase().includes('cusdc') && tokenOutAddress === '0xf22258f57794cc8e06237084b353ab30fffa640b'
      || key.toLowerCase().includes('usdt') && tokenOutAddress === '0x004b63a7b5b0e06f6bb6adb4a5f9f590bf3182d1'
      || key.toLowerCase().includes('usdc') && tokenOutAddress === '0x71d6687f38b93ccad569fa6352c876eea967201b'
      || key.toLowerCase().includes('ceurt') && tokenOutAddress === '0xdf4b71c61e5912712c1bdd451416b9ac26949d72'
      || key.toLowerCase().includes('cxauc') && tokenOutAddress === '0x290e52a8819a4fbd0714e517225429aa2b70ec6b';
  });

  if (!reserveInEntry || !reserveOutEntry) return undefined;

  const reserveIn = BigInt(reserveInEntry[1].replace('.', ''));
  const reserveOut = BigInt(reserveOutEntry[1].replace('.', ''));
  return quoteAmountOut(amountIn, reserveIn, reserveOut).toString();
}

export function buildInternalExecutionPlan(
  request: InternalExecutionPlanRequest
): { plan?: InternalExecutionPlan; error?: string; candidateRouteIds?: string[] } {
  const matrix = loadAggregatorRouteMatrix();
  if (!matrix) {
    return { error: 'Aggregator route matrix not available' };
  }

  const routes = matrix.liveSwapRoutes.filter((route) => route.fromChainId === 138 && route.toChainId === 138);
  const route = findRoute(request, routes);
  if (!route) {
    const candidateRouteIds = routes
      .filter((candidate) =>
        !request.fromChainId || candidate.fromChainId === request.fromChainId
      )
      .map((candidate) => candidate.routeId);
    return {
      error: request.routeId
        ? 'No live internal route found for routeId'
        : 'Internal execution planning requires exactly one matching live Chain 138 swap route',
      candidateRouteIds,
    };
  }

  const reserveMap = buildPoolReserveMap(matrix.liveSwapRoutes);
  const amountIn = BigInt(request.amountIn);
  const slippageBps = request.slippageBps || '100';
  const executorAddress = route.legs?.[0]?.executorAddress || '0x86ADA6Ef91A3B450F89f2b751e93B1b7A3218895';

  const steps: InternalExecutionStep[] = [];
  let currentAmount = amountIn;

  for (let i = 0; i < (route.legs || []).length; i += 1) {
    const leg = route.legs?.[i];
    if (!leg || !leg.poolAddress || !leg.tokenInAddress) continue;

    steps.push({
      kind: 'approve',
      description: `Approve token for DODOPMMIntegration before leg ${i + 1}`,
      tokenAddress: leg.tokenInAddress,
      spender: executorAddress,
      amountSource: i === 0 ? 'user_input' : 'previous_step_output',
    });

    const estimatedOut = estimateLegOut(currentAmount, leg, reserveMap);
    const minAmountOut = estimatedOut
      ? ((BigInt(estimatedOut) * (10_000n - BigInt(slippageBps))) / 10_000n).toString()
      : '0';

    steps.push({
      kind: 'swap',
      description: `Execute DODO PMM swap leg ${i + 1}`,
      contractAddress: executorAddress,
      functionName: 'swapExactIn',
      signature: 'swapExactIn(address,address,uint256,uint256)',
      args: [
        leg.poolAddress,
        leg.tokenInAddress,
        i === 0 ? request.amountIn : { source: 'previous_step_output' },
        minAmountOut,
      ],
      amountSource: i === 0 ? 'user_input' : 'previous_step_output',
      estimatedAmountOut: estimatedOut,
      minAmountOut,
    });

    if (estimatedOut) {
      currentAmount = BigInt(estimatedOut);
    }
  }

  return {
    plan: {
      routeId: route.routeId,
      chainId: 138,
      executor: {
        protocol: 'dodo_pmm',
        contractAddress: executorAddress,
      },
      amountIn: request.amountIn,
      slippageBps,
      notes: [
        'Fallback plan is for internal Chain 138 execution via DODOPMMIntegration.',
        'Estimated outputs use the service quote approximation and should be treated as guidance.',
        'Approve steps are included explicitly because DODOPMMIntegration pulls tokens with transferFrom.',
      ],
      steps,
    },
  };
}
