/**
 * Create and seed Uniswap V2 pairs on Chain 138: each canonical asset vs WETH9 (ERC-20) in the pair.
 *
 * Funding the ETH leg (no explicit wrap in this script):
 *   - Liquidity is added via UniswapV2Router02.addLiquidityETH: you send native ETH as `msg.value`.
 *   - The router wraps to WETH9 inside the call and mints LP; this script never calls WETH9.deposit().
 *   - Pair reserves are still WETH + token (AMM math cannot store raw ETH in the pair contract).
 *
 * Prerequisites:
 *   - PRIVATE_KEY; deployer holds native ETH (for `value` + gas) + tokens (or MINTER_ROLE to mint).
 *   - Factory + router: CHAIN138_UNISWAP_V2_EXISTING_* or deployments/chain138/uniswap-v2-native.json
 *
 * Usage (from smom-dbis-138):
 *   source scripts/load-env.sh
 *   npx hardhat run scripts/chain138/seed-uni-v2-weth-quote-pairs.js --network chain138
 *
 * Tuning:
 *   CHAIN138_NATIVE_WETH9 — must match router’s WETH (default 0xC02a…)
 *   CHAIN138_UNI_V2_DEFAULT_WETH — native ETH `value` per new pair (default 0.25)
 *   CHAIN138_UNI_V2_STABLE_PER_WETH — USD face (6-decimal) per 1 ETH for stables (default 2100)
 *   CHAIN138_UNI_V2_SLIPPAGE_BPS — min amounts for addLiquidityETH (default 50 = 0.5%)
 *   CHAIN138_UNI_V2_ETH_GAS_BUFFER_WEI — extra wei required on top of `value` (default ~0.03 ETH for gas)
 *   Per-pair: CHAIN138_UNI_V2_SEED_<key>_WETH and CHAIN138_UNI_V2_SEED_<key>_TOKEN
 */
const fs = require("fs");
const path = require("path");
const hre = require("hardhat");

const WETH9 = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";

/** Canonical Chain 138 tokens (see docs/11-references/EXPLORER_TOKEN_LIST_CROSSCHECK.md). */
const WETH_QUOTE_TARGETS = [
  { key: "wethLink", symbol: "LINK", address: "0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03", decimals: 18, kind: "link" },
  { key: "wethCusdt", symbol: "cUSDT", address: "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22", decimals: 6, kind: "stable6" },
  { key: "wethCusdc", symbol: "cUSDC", address: "0xf22258f57794CC8E06237084b353Ab30fFfa640b", decimals: 6, kind: "stable6" },
  { key: "wethWeth10", symbol: "WETH10", address: "0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f", decimals: 18, kind: "weth10" },
  { key: "wethCeurc", symbol: "cEURC", address: "0x8085961F9cF02b4d800A3c6d386D31da4B34266a", decimals: 6, kind: "stable6" },
  { key: "wethCeurt", symbol: "cEURT", address: "0xdf4b71c61E5912712C1Bdd451416B9aC26949d72", decimals: 6, kind: "stable6" },
  { key: "wethCgbpc", symbol: "cGBPC", address: "0x003960f16D9d34F2e98d62723B6721Fb92074aD2", decimals: 6, kind: "stable6" },
  { key: "wethCgbrt", symbol: "cGBPT", address: "0x350f54e4D23795f86A9c03988c7135357CCaD97c", decimals: 6, kind: "stable6" },
  { key: "wethCaudc", symbol: "cAUDC", address: "0xD51482e567c03899eecE3CAe8a058161FD56069D", decimals: 6, kind: "stable6" },
  { key: "wethCjpyc", symbol: "cJPYC", address: "0xEe269e1226a334182aace90056EE4ee5Cc8A6770", decimals: 6, kind: "stable6" },
  { key: "wethCchfc", symbol: "cCHFC", address: "0x873990849DDa5117d7C644f0aF24370797C03885", decimals: 6, kind: "stable6" },
  { key: "wethCcadc", symbol: "cCADC", address: "0x54dBd40cF05e15906A2C21f600937e96787f5679", decimals: 6, kind: "stable6" },
  { key: "wethCxauc", symbol: "cXAUC", address: "0x290E52a8819A4fbD0714E517225429aA2B70EC6b", decimals: 6, kind: "xau6" },
  { key: "wethCxaut", symbol: "cXAUT", address: "0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E", decimals: 6, kind: "xau6" },
  { key: "wethUsdt", symbol: "USDT", address: "0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1", decimals: 6, kind: "stable6" },
  { key: "wethUsdc", symbol: "USDC", address: "0x71D6687F38b93CCad569Fa6352c876eea967201b", decimals: 6, kind: "stable6" },
];

const erc20Abi = [
  "function transfer(address to,uint256 amount) returns (bool)",
  "function approve(address spender,uint256 amount) returns (bool)",
  "function balanceOf(address account) view returns (uint256)",
];

const routerAbi = [
  "function addLiquidityETH(address token,uint amountTokenDesired,uint amountTokenMin,uint amountETHMin,address to,uint deadline) payable returns (uint amountToken, uint amountETH, uint liquidity)",
];

const mintAbi = ["function mint(address to,uint256 amount)"];

function defaultWethFloat() {
  return parseFloat(process.env.CHAIN138_UNI_V2_DEFAULT_WETH || "0.25");
}

function stablePerWethUsd() {
  return parseFloat(process.env.CHAIN138_UNI_V2_STABLE_PER_WETH || "2100");
}

async function ensureMinted(tokenAddr, signer, need) {
  const c = await hre.ethers.getContractAt(erc20Abi, tokenAddr, signer);
  const bal = await c.balanceOf(signer.address);
  if (bal >= need) return;
  const short = need - bal;
  const m = await hre.ethers.getContractAt(mintAbi, tokenAddr, signer);
  await (await m.mint(signer.address, short)).wait();
}

async function ensureBalance(tokenAddr, signer, need, meta) {
  const c = await hre.ethers.getContractAt(erc20Abi, tokenAddr, signer);
  const bal = await c.balanceOf(signer.address);
  if (bal >= need) return;
  try {
    await ensureMinted(tokenAddr, signer, need);
  } catch (e) {
    throw new Error(
      `${meta.symbol}: need ${hre.ethers.formatUnits(need - bal, meta.decimals)} more; mint failed: ${e.message}`
    );
  }
}

/** Ensure wallet has enough native ETH for addLiquidityETH `value` plus a gas buffer (this script does not wrap). */
async function ensureNativeEth(signer, valueWei) {
  const buffer = BigInt(process.env.CHAIN138_UNI_V2_ETH_GAS_BUFFER_WEI || "30000000000000000");
  const need = valueWei + buffer;
  const bal = await signer.provider.getBalance(signer.address);
  if (bal < need) {
    throw new Error(
      `Native ETH: need at least ${hre.ethers.formatEther(need)} (${hre.ethers.formatEther(valueWei)} for pool + buffer); have ${hre.ethers.formatEther(bal)}`
    );
  }
}

function computeAmounts(meta) {
  const envW = process.env[`CHAIN138_UNI_V2_SEED_${meta.key}_WETH`];
  const envT = process.env[`CHAIN138_UNI_V2_SEED_${meta.key}_TOKEN`];

  const ethWei =
    envW !== undefined && envW !== ""
      ? hre.ethers.parseEther(String(envW))
      : hre.ethers.parseEther(String(defaultWethFloat()));

  const wethEth = parseFloat(hre.ethers.formatEther(ethWei));

  let tokenStr;
  if (envT !== undefined && envT !== "") {
    tokenStr = String(envT);
  } else {
    switch (meta.kind) {
      case "stable6":
        tokenStr = String(Math.max(1, Math.round(stablePerWethUsd() * wethEth)));
        break;
      case "link":
        tokenStr = String(Math.max(1, Math.round(100 * wethEth)));
        break;
      case "weth10":
        tokenStr = hre.ethers.formatEther(ethWei);
        break;
      case "xau6":
        tokenStr = String(Math.max(1, Math.round(1 * Math.max(wethEth, 0.01))));
        break;
      default:
        tokenStr = "1";
    }
  }

  const tokenAmt = hre.ethers.parseUnits(tokenStr, meta.decimals);
  return { ethWei, tokenAmt };
}

function minAmounts(amount, bps) {
  return (amount * (10000n - bps)) / 10000n;
}

async function main() {
  const signers = await hre.ethers.getSigners();
  if (!signers?.length) {
    throw new Error("chain138: no signer — set PRIVATE_KEY (32-byte hex) for network chain138 in env");
  }
  const deployer = signers[0];
  const gasPrice = BigInt(process.env.CHAIN138_NATIVE_V2_GAS_PRICE || (await hre.ethers.provider.send("eth_gasPrice", [])));
  const txOverrides = {
    type: 0,
    gasPrice,
    gasLimit: BigInt(process.env.CHAIN138_NATIVE_V2_LIQUIDITY_GAS_LIMIT || "3000000"),
  };

  const deploymentPath = path.resolve(__dirname, "../../deployments/chain138/uniswap-v2-native.json");
  let factory = (process.env.CHAIN138_UNISWAP_V2_EXISTING_FACTORY || "").trim();
  let routerAddr = (process.env.CHAIN138_UNISWAP_V2_EXISTING_ROUTER || "").trim();
  if (fs.existsSync(deploymentPath)) {
    const j = JSON.parse(fs.readFileSync(deploymentPath, "utf8"));
    if (!factory) factory = j.factory;
    if (!routerAddr) routerAddr = j.router;
  }
  if (!factory) {
    throw new Error("Set CHAIN138_UNISWAP_V2_EXISTING_FACTORY or add deployments/chain138/uniswap-v2-native.json");
  }
  if (!routerAddr) {
    throw new Error("Set CHAIN138_UNISWAP_V2_EXISTING_ROUTER or add router to deployments/chain138/uniswap-v2-native.json");
  }

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

  const factoryC = Factory.attach(factory);
  const wethAddr = (process.env.CHAIN138_NATIVE_WETH9 || process.env.CHAIN138_WETH9_ADDRESS || WETH9).trim();
  const router = await hre.ethers.getContractAt(routerAbi, routerAddr, deployer);

  console.log(
    `[eth] add liquidity via router addLiquidityETH (native ETH \`value\`; no WETH9.deposit() in this script) router=${routerAddr}`
  );
  console.log(`[weth9] pair reserve token: ${wethAddr} (router wraps ETH inside addLiquidityETH)`);

  const slipBps = BigInt(process.env.CHAIN138_UNI_V2_SLIPPAGE_BPS || "50");
  const skipKeys = new Set(
    (process.env.CHAIN138_UNI_V2_SKIP_KEYS || "")
      .split(",")
      .map((s) => s.trim())
      .filter(Boolean)
  );

  /** @type {{ key: string, pair: string, status: string }[]} */
  const report = [];

  for (const meta of WETH_QUOTE_TARGETS) {
    if (skipKeys.has(meta.key)) {
      report.push({ key: meta.key, pair: "", status: "skip_env" });
      console.log(`[skip] ${meta.symbol} (${meta.key}) via CHAIN138_UNI_V2_SKIP_KEYS`);
      continue;
    }
    const tokenAddr = meta.address;
    if (tokenAddr.toLowerCase() === wethAddr.toLowerCase()) continue;

    const { ethWei, tokenAmt } = computeAmounts(meta);

    let pairAddress = await factoryC.getPair(wethAddr, tokenAddr);
    if (pairAddress === hre.ethers.ZeroAddress) {
      const tx = await factoryC.createPair(wethAddr, tokenAddr, txOverrides);
      await tx.wait();
      pairAddress = await factoryC.getPair(wethAddr, tokenAddr);
    }

    const pair = Pair.connect(deployer).attach(pairAddress);
    const [r0, r1] = await pair.getReserves();
    if (r0 > 0n || r1 > 0n) {
      report.push({ key: meta.key, pair: pairAddress, status: "skip_existing_liquidity" });
      console.log(`[skip] ${meta.symbol} already funded ${pairAddress}`);
      continue;
    }

    const tokenC = await hre.ethers.getContractAt(erc20Abi, tokenAddr, deployer);

    await ensureNativeEth(deployer, ethWei);
    await ensureBalance(tokenAddr, deployer, tokenAmt, meta);

    const allowance = await tokenC.allowance(deployer.address, routerAddr);
    if (allowance < tokenAmt) {
      await (await tokenC.approve(routerAddr, hre.ethers.MaxUint256)).wait();
    }

    const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600);
    const tokenMin = minAmounts(tokenAmt, slipBps);
    const ethMin = minAmounts(ethWei, slipBps);

    console.log(`[eth] addLiquidityETH ${meta.symbol}: value=${hre.ethers.formatEther(ethWei)} ETH, token=${tokenAmt.toString()}`);
    const tx = await router.addLiquidityETH(
      tokenAddr,
      tokenAmt,
      tokenMin,
      ethMin,
      deployer.address,
      deadline,
      { ...txOverrides, value: ethWei }
    );
    await tx.wait();

    report.push({ key: meta.key, pair: pairAddress, status: "seeded" });
    console.log(`[ok] ${meta.symbol} ${pairAddress}`);
  }

  const out = {
    chainId: 138,
    deployer: deployer.address,
    factory,
    router: routerAddr,
    weth9: wethAddr,
    results: report,
  };
  const outPath = path.resolve(__dirname, "../../deployments/chain138/uni-v2-weth-quote-seed.json");
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
  fs.writeFileSync(outPath, JSON.stringify(out, null, 2) + "\n");
  console.log(JSON.stringify(out, null, 2));
}

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