import { AbiCoder, Contract, JsonRpcProvider, formatUnits } from 'ethers';
import { resolveChain138RpcUrl } from '../config/chain138-rpc';
import { SwapGraphEdge } from './planner-v2-types';

const CHAIN_138 = 138;
const DEFAULT_D3_POOL = '0x6550A3a59070061a262a893A1D6F3F490afFDBDA';
const DEFAULT_D3_PROXY = '0xc9a11abB7C63d88546Be24D58a6d95e3762cB843';
const DEFAULT_ROUTER_V2 = '0xF1c93F54A5C2fc0d7766Ccb0Ad8f157DFB4C99Ce';
const DEFAULT_WETH10 = '0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f';
const DEFAULT_USDT = '0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1';
const abiCoder = AbiCoder.defaultAbiCoder();

const d3MmAbi = [
  'function getTokenReserve(address token) view returns (uint256)',
  'function querySellTokens(address fromToken,address toToken,uint256 fromAmount) view returns (uint256,uint256,uint256,uint256,uint256)',
] as const;

export interface DodoV3PilotConfig {
  enabled: boolean;
  chainId: number;
  rpcUrl: string;
  poolAddress: string;
  proxyAddress: string;
  weth10Address: string;
  usdtAddress: string;
  wethUsdPrice: number;
  liquidityOverrideUsd?: number;
}

function normalizeAddress(value?: string): string {
  return String(value || '').trim().toLowerCase();
}

function normalizeNumber(value?: string): number | undefined {
  const parsed = Number(value || '');
  return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
}

export function encodeChain138DodoV3ProviderData(poolAddress: string): string {
  return abiCoder.encode(['address'], [normalizeAddress(poolAddress)]);
}

export function isChain138DodoV3ExecutionLive(): boolean {
  const enabled = process.env.CHAIN138_ENABLE_DODO_V3_EXECUTION !== '0';
  const router = normalizeAddress(process.env.ENHANCED_SWAP_ROUTER_V2_ADDRESS || DEFAULT_ROUTER_V2);
  return enabled && Boolean(router);
}

function getPilotConfig(): DodoV3PilotConfig {
  return {
    enabled: process.env.CHAIN138_ENABLE_DODO_V3_ROUTING !== '0',
    chainId: CHAIN_138,
    rpcUrl: resolveChain138RpcUrl(),
    poolAddress: normalizeAddress(process.env.CHAIN138_D3_MM_ADDRESS || DEFAULT_D3_POOL),
    proxyAddress: normalizeAddress(process.env.CHAIN138_D3_PROXY_ADDRESS || DEFAULT_D3_PROXY),
    weth10Address: normalizeAddress(process.env.CHAIN138_D3_WETH10_ADDRESS || process.env.WETH10_ADDRESS || DEFAULT_WETH10),
    usdtAddress: normalizeAddress(process.env.CHAIN138_D3_USDT_ADDRESS || process.env.USDT_ADDRESS_138 || DEFAULT_USDT),
    wethUsdPrice: normalizeNumber(process.env.CHAIN138_D3_PILOT_WETH_USD)?.valueOf() || 2100,
    liquidityOverrideUsd: normalizeNumber(process.env.CHAIN138_D3_PILOT_TOTAL_LIQUIDITY_USD),
  };
}

let sharedProvider: JsonRpcProvider | null = null;
let sharedProviderUrl = '';

function getProvider(rpcUrl: string): JsonRpcProvider {
  if (!sharedProvider || sharedProviderUrl !== rpcUrl) {
    sharedProvider = new JsonRpcProvider(rpcUrl);
    sharedProviderUrl = rpcUrl;
  }
  return sharedProvider;
}

function buildNotes(poolAddress: string, proxyAddress: string, executionLive: boolean): string[] {
  return [
    'DODO v3 / D3MM Chain 138 pilot venue.',
    `Canonical private pilot pool ${poolAddress} executes through D3Proxy ${proxyAddress}.`,
    executionLive
      ? 'Planner-v2 exposure and EnhancedSwapRouterV2 internal execution-plan calldata are live for the canonical pilot pair.'
      : 'Planner-v2 exposure is live, but EnhancedSwapRouterV2 execution is disabled in the local environment.',
  ];
}

function approximateLiquidityUsd(args: {
  wethReserve: bigint;
  usdtReserve: bigint;
  wethUsdPrice: number;
  liquidityOverrideUsd?: number;
}): number {
  if (args.liquidityOverrideUsd && Number.isFinite(args.liquidityOverrideUsd)) {
    return args.liquidityOverrideUsd;
  }

  const wethSideUsd = Number(formatUnits(args.wethReserve, 18)) * args.wethUsdPrice;
  const usdtSideUsd = Number(formatUnits(args.usdtReserve, 6));
  return Number((wethSideUsd + usdtSideUsd).toFixed(2));
}

function isCanonicalPilotPair(config: DodoV3PilotConfig, tokenInAddress: string, tokenOutAddress: string): boolean {
  const tokenIn = normalizeAddress(tokenInAddress);
  const tokenOut = normalizeAddress(tokenOutAddress);
  return (
    (tokenIn === config.weth10Address && tokenOut === config.usdtAddress) ||
    (tokenIn === config.usdtAddress && tokenOut === config.weth10Address)
  );
}

export async function quoteChain138DodoV3AmountOut(args: {
  tokenInAddress: string;
  tokenOutAddress: string;
  amountIn: bigint | string;
}): Promise<bigint> {
  const config = getPilotConfig();
  if (!config.enabled || !config.poolAddress || !config.proxyAddress) {
    return 0n;
  }
  if (!isCanonicalPilotPair(config, args.tokenInAddress, args.tokenOutAddress)) {
    return 0n;
  }

  const pool = new Contract(config.poolAddress, d3MmAbi, getProvider(config.rpcUrl));
  const quote = await pool.querySellTokens(
    normalizeAddress(args.tokenInAddress),
    normalizeAddress(args.tokenOutAddress),
    BigInt(args.amountIn)
  );

  return BigInt(String(quote[1]));
}

export async function getChain138DodoV3PilotEdges(): Promise<SwapGraphEdge[]> {
  const config = getPilotConfig();
  if (!config.enabled || !config.poolAddress || !config.proxyAddress || !config.weth10Address || !config.usdtAddress) {
    return [];
  }
  const executionLive = isChain138DodoV3ExecutionLive();

  const pool = new Contract(config.poolAddress, d3MmAbi, getProvider(config.rpcUrl));
  const [wethReserveRaw, usdtReserveRaw] = await Promise.all([
    pool.getTokenReserve(config.weth10Address),
    pool.getTokenReserve(config.usdtAddress),
  ]);

  const wethReserve = BigInt(String(wethReserveRaw));
  const usdtReserve = BigInt(String(usdtReserveRaw));
  if (wethReserve <= 0n || usdtReserve <= 0n) {
    return [];
  }

  const totalLiquidityUsd = approximateLiquidityUsd({
    wethReserve,
    usdtReserve,
    wethUsdPrice: config.wethUsdPrice,
    liquidityOverrideUsd: config.liquidityOverrideUsd,
  });
  const notes = buildNotes(config.poolAddress, config.proxyAddress, executionLive);

  return [
    {
      kind: 'swap',
      provider: 'dodo_v3',
      chainId: CHAIN_138,
      tokenInAddress: config.weth10Address,
      tokenOutAddress: config.usdtAddress,
      tokenInSymbol: 'WETH10',
      tokenOutSymbol: 'USDT',
      reserveIn: wethReserve.toString(),
      reserveOut: usdtReserve.toString(),
      target: config.proxyAddress,
      poolAddress: config.poolAddress,
      providerData: {
        poolAddress: config.poolAddress,
        quoteMethod: 'querySellTokens',
        executionTarget: config.proxyAddress,
      },
      providerDataHex: executionLive ? encodeChain138DodoV3ProviderData(config.poolAddress) : undefined,
      totalLiquidityUsd,
      freshnessSeconds: 0,
      notes,
    },
    {
      kind: 'swap',
      provider: 'dodo_v3',
      chainId: CHAIN_138,
      tokenInAddress: config.usdtAddress,
      tokenOutAddress: config.weth10Address,
      tokenInSymbol: 'USDT',
      tokenOutSymbol: 'WETH10',
      reserveIn: usdtReserve.toString(),
      reserveOut: wethReserve.toString(),
      target: config.proxyAddress,
      poolAddress: config.poolAddress,
      providerData: {
        poolAddress: config.poolAddress,
        quoteMethod: 'querySellTokens',
        executionTarget: config.proxyAddress,
      },
      providerDataHex: executionLive ? encodeChain138DodoV3ProviderData(config.poolAddress) : undefined,
      totalLiquidityUsd,
      freshnessSeconds: 0,
      notes,
    },
  ];
}
