const fs = require("fs");
const path = require("path");
const hre = require("hardhat");

const DEFAULTS = {
  weth: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
  usdt: "0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1",
  usdc: "0x71D6687F38b93CCad569Fa6352c876eea967201b",
  cusdt: "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22",
  cusdc: "0xf22258f57794CC8E06237084b353Ab30fFfa640b",
};

function envAddress(name, fallback) {
  const value = String(process.env[name] || fallback || "").trim();
  if (!value) {
    throw new Error(`Missing required address env ${name}`);
  }
  return value;
}

function envUnits(name, fallback, decimals) {
  return hre.ethers.parseUnits(String(process.env[name] || fallback), decimals);
}

async function ensureAllowance(token, owner, spender, amount) {
  const allowance = await token.allowance(owner, spender);
  if (allowance >= amount) return;
  const tx = await token.approve(spender, hre.ethers.MaxUint256);
  await tx.wait();
}

async function transferIfNeeded(token, to, amount) {
  if (amount <= 0n) return;
  const tx = await token.transfer(to, amount);
  await tx.wait();
}

async function main() {
  const [deployer] = await hre.ethers.getSigners();
  const currentBlock = await hre.ethers.provider.getBlockNumber();
  const gasPrice = BigInt(process.env.CHAIN138_NATIVE_V2_GAS_PRICE || (await hre.ethers.provider.send("eth_gasPrice", [])));
  const deployOverrides = {
    type: 0,
    gasPrice,
    gasLimit: BigInt(process.env.CHAIN138_NATIVE_V2_DEPLOY_GAS_LIMIT || "9000000"),
  };
  const liquidityOverrides = {
    type: 0,
    gasPrice,
    gasLimit: BigInt(process.env.CHAIN138_NATIVE_V2_LIQUIDITY_GAS_LIMIT || "3000000"),
  };

  const weth = envAddress("CHAIN138_NATIVE_WETH9", process.env.WETH9 || DEFAULTS.weth);
  const usdt = envAddress("CHAIN138_NATIVE_USDT", process.env.OFFICIAL_USDT_ADDRESS || DEFAULTS.usdt);
  const usdc = envAddress("CHAIN138_NATIVE_USDC", process.env.OFFICIAL_USDC_ADDRESS || DEFAULTS.usdc);
  const cusdt = envAddress("CHAIN138_COMPLIANT_USDT", process.env.COMPLIANT_USDT_ADDRESS || DEFAULTS.cusdt);
  const cusdc = envAddress("CHAIN138_COMPLIANT_USDC", process.env.COMPLIANT_USDC_ADDRESS || DEFAULTS.cusdc);

  const Factory = await hre.ethers.getContractFactory(
    "contracts/vendor/uniswap-v2-core/UniswapV2Factory.sol:UniswapV2Factory"
  );
  const Router = await hre.ethers.getContractFactory(
    "contracts/vendor/uniswap-v2-periphery/UniswapV2Router02.sol:UniswapV2Router02"
  );
  const Pair = await hre.ethers.getContractFactory(
    "contracts/vendor/uniswap-v2-core/UniswapV2Pair.sol:UniswapV2Pair"
  );

  let factory;
  let router;

  const existingFactory = process.env.CHAIN138_UNISWAP_V2_EXISTING_FACTORY?.trim();
  const existingRouter = process.env.CHAIN138_UNISWAP_V2_EXISTING_ROUTER?.trim();
  if (existingFactory && existingRouter) {
    factory = Factory.attach(existingFactory);
    router = Router.attach(existingRouter);
    console.log(`[reuse] factory=${existingFactory}`);
    console.log(`[reuse] router=${existingRouter}`);
  } else {
    console.log(`[deploy] UniswapV2Factory gasPrice=${gasPrice}`);
    factory = await Factory.deploy(deployer.address, deployOverrides);
    await factory.waitForDeployment();
    console.log(`[ok] factory=${await factory.getAddress()}`);
    console.log(`[deploy] UniswapV2Router02`);
    router = await Router.deploy(await factory.getAddress(), weth, deployOverrides);
    await router.waitForDeployment();
    console.log(`[ok] router=${await router.getAddress()}`);
  }

  const erc20Abi = [
    "function transfer(address to,uint256 amount) returns (bool)",
    "function approve(address spender,uint256 amount) returns (bool)",
    "function allowance(address owner,address spender) view returns (uint256)",
    "function balanceOf(address account) view returns (uint256)",
  ];
  const tokenByAddress = {
    [weth.toLowerCase()]: new hre.ethers.Contract(weth, erc20Abi, deployer),
    [usdt.toLowerCase()]: new hre.ethers.Contract(usdt, erc20Abi, deployer),
    [usdc.toLowerCase()]: new hre.ethers.Contract(usdc, erc20Abi, deployer),
    [cusdt.toLowerCase()]: new hre.ethers.Contract(cusdt, erc20Abi, deployer),
    [cusdc.toLowerCase()]: new hre.ethers.Contract(cusdc, erc20Abi, deployer),
  };

  const seedSpecs = [
    {
      key: "wethUsdt",
      tokenA: weth,
      tokenB: usdt,
      amountA: envUnits("CHAIN138_UNISWAP_V2_SEED_WETH_USDT_WETH", "25", 18),
      amountB: envUnits("CHAIN138_UNISWAP_V2_SEED_WETH_USDT_STABLE", "52915", 6),
    },
    {
      key: "wethUsdc",
      tokenA: weth,
      tokenB: usdc,
      amountA: envUnits("CHAIN138_UNISWAP_V2_SEED_WETH_USDC_WETH", "25", 18),
      amountB: envUnits("CHAIN138_UNISWAP_V2_SEED_WETH_USDC_STABLE", "52915", 6),
    },
    {
      key: "cusdtCusdc",
      tokenA: cusdt,
      tokenB: cusdc,
      amountA: envUnits("CHAIN138_UNISWAP_V2_SEED_CUSDT_CUSDC_CUSDT", "250000", 6),
      amountB: envUnits("CHAIN138_UNISWAP_V2_SEED_CUSDT_CUSDC_CUSDC", "250000", 6),
    },
  ];

  const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600);
  const deployedPairs = {};

  for (const spec of seedSpecs) {
    console.log(`[seed] ${spec.key}`);
    let pairAddress = await factory.getPair(spec.tokenA, spec.tokenB);
    if (pairAddress === hre.ethers.ZeroAddress) {
      const tx = await factory.createPair(spec.tokenA, spec.tokenB, liquidityOverrides);
      await tx.wait();
      pairAddress = await factory.getPair(spec.tokenA, spec.tokenB);
    }

    const pair = Pair.attach(pairAddress);
    const [reserve0, reserve1] = await pair.getReserves();
    if (reserve0 > 0n || reserve1 > 0n) {
      deployedPairs[spec.key] = pairAddress;
      console.log(`[skip] ${spec.key} already seeded ${pairAddress}`);
      continue;
    }

    const currentABalance = await tokenByAddress[spec.tokenA.toLowerCase()].balanceOf(pairAddress);
    const currentBBalance = await tokenByAddress[spec.tokenB.toLowerCase()].balanceOf(pairAddress);
    const topUpA = spec.amountA > currentABalance ? spec.amountA - currentABalance : 0n;
    const topUpB = spec.amountB > currentBBalance ? spec.amountB - currentBBalance : 0n;

    await transferIfNeeded(tokenByAddress[spec.tokenA.toLowerCase()], pairAddress, topUpA);
    await transferIfNeeded(tokenByAddress[spec.tokenB.toLowerCase()], pairAddress, topUpB);

    const mintTx = await pair.mint(deployer.address, liquidityOverrides);
    await mintTx.wait();
    deployedPairs[spec.key] = pairAddress;
    console.log(`[ok] ${spec.key}=${deployedPairs[spec.key]}`);
  }

  const output = {
    chainId: 138,
    deployer: deployer.address,
    deployedAtBlock: currentBlock,
    factory: await factory.getAddress(),
    router: await router.getAddress(),
    weth,
    usdt,
    usdc,
    cusdt,
    cusdc,
    pairs: deployedPairs,
  };

  const outputPath = path.resolve(__dirname, "../../deployments/chain138/uniswap-v2-native.json");
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
  fs.writeFileSync(outputPath, JSON.stringify(output, null, 2) + "\n");

  console.log(JSON.stringify(output, null, 2));
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
