import { expect } from "chai";
import { ethers } from "hardhat";
import { {{CONTRACT_NAME}} } from "../typechain-types";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";

describe("{{CONTRACT_NAME}}", function () {
  let diceGame: {{CONTRACT_NAME}};
  let owner: HardhatEthersSigner;
  let player1: HardhatEthersSigner;
  let player2: HardhatEthersSigner;

  const MIN_BET = ethers.parseEther("0.01");
  const MAX_BET = ethers.parseEther("10");
  const HOUSE_EDGE = 250; // 2.5%

  beforeEach(async function () {
    [owner, player1, player2] = await ethers.getSigners();

    const DiceGameFactory = await ethers.getContractFactory("{{CONTRACT_NAME}}");
    diceGame = await DiceGameFactory.deploy(MIN_BET, MAX_BET, HOUSE_EDGE);
    await diceGame.waitForDeployment();

    // Fund the house
    await diceGame.fundHouse({ value: ethers.parseEther("100") });
  });

  describe("Deployment", function () {
    it("Should set the correct min bet", async function () {
      expect(await diceGame.minBet()).to.equal(MIN_BET);
    });

    it("Should set the correct max bet", async function () {
      expect(await diceGame.maxBet()).to.equal(MAX_BET);
    });

    it("Should set the correct house edge", async function () {
      expect(await diceGame.houseEdge()).to.equal(HOUSE_EDGE);
    });

    it("Should start with zero games", async function () {
      expect(await diceGame.getGameCount()).to.equal(0);
    });
  });

  describe("Payout Multipliers", function () {
    it("Should have correct over/under payout (1.95x)", async function () {
      expect(await diceGame.OVER_UNDER_PAYOUT()).to.equal(19500);
    });

    it("Should have correct exact payout (5x)", async function () {
      expect(await diceGame.EXACT_PAYOUT()).to.equal(50000);
    });

    it("Should have correct high/low payout (1.9x)", async function () {
      expect(await diceGame.HIGH_LOW_PAYOUT()).to.equal(19000);
    });
  });

  describe("Payout Calculation", function () {
    it("Should calculate over/under payout correctly", async function () {
      const bet = ethers.parseEther("1");
      const payout = await diceGame.calculatePayout(0, bet); // 0 = OverUnder
      expect(payout).to.equal(ethers.parseEther("1.95"));
    });

    it("Should calculate exact payout correctly", async function () {
      const bet = ethers.parseEther("1");
      const payout = await diceGame.calculatePayout(1, bet); // 1 = Exact
      expect(payout).to.equal(ethers.parseEther("5"));
    });

    it("Should calculate high/low payout correctly", async function () {
      const bet = ethers.parseEther("1");
      const payout = await diceGame.calculatePayout(2, bet); // 2 = HighLow
      expect(payout).to.equal(ethers.parseEther("1.9"));
    });
  });

  describe("Betting", function () {
    it("Should place bet and create game", async function () {
      const bet = ethers.parseEther("0.1");
      await expect(diceGame.connect(player1).placeBet(0, 7, { value: bet }))
        .to.emit(diceGame, "BetPlaced")
        .and.to.emit(diceGame, "DiceRolled");

      expect(await diceGame.getGameCount()).to.equal(1);
    });

    it("Should reject bet below minimum", async function () {
      const lowBet = MIN_BET - 1n;
      await expect(diceGame.connect(player1).placeBet(0, 7, { value: lowBet }))
        .to.be.revertedWithCustomError(diceGame, "InsufficientBet");
    });

    it("Should reject bet above maximum", async function () {
      const highBet = MAX_BET + 1n;
      await expect(diceGame.connect(player1).placeBet(0, 7, { value: highBet }))
        .to.be.revertedWithCustomError(diceGame, "InsufficientBet");
    });
  });

  describe("Game Info", function () {
    beforeEach(async function () {
      await diceGame.connect(player1).placeBet(0, 7, { value: MIN_BET });
    });

    it("Should return correct game info", async function () {
      const [player, betType, prediction, betAmount, revealRequested, revealed] = await diceGame.getGame(0);
      expect(player).to.equal(player1.address);
      expect(betType).to.equal(0); // OverUnder
      expect(prediction).to.equal(7);
      expect(betAmount).to.equal(MIN_BET);
      expect(revealRequested).to.equal(false);
      expect(revealed).to.equal(false);
    });
  });

  describe("Result Reveal (Public Decryption)", function () {
    beforeEach(async function () {
      await diceGame.connect(player1).placeBet(0, 7, { value: MIN_BET });
    });

    it("Should request result reveal", async function () {
      await expect(diceGame.connect(player1).requestResultReveal(0))
        .to.emit(diceGame, "ResultReadyForReveal")
        .withArgs(0);

      const [, , , , revealRequested] = await diceGame.getGame(0);
      expect(revealRequested).to.equal(true);
    });

    it("Should reject request from non-player", async function () {
      await expect(diceGame.connect(player2).requestResultReveal(0))
        .to.be.revertedWithCustomError(diceGame, "NotGamePlayer");
    });

    it("Should reject double reveal request", async function () {
      await diceGame.connect(player1).requestResultReveal(0);
      await expect(diceGame.connect(player1).requestResultReveal(0))
        .to.be.revertedWithCustomError(diceGame, "GameAlreadyRevealed");
    });

    it("Should provide win handle after bet", async function () {
      const winHandle = await diceGame.getWinHandle(0);
      // Handle should be non-zero (it's an encrypted value reference)
      expect(winHandle).to.not.equal(0);
    });

    it("Should reject getWinHandle for non-existent game", async function () {
      await expect(diceGame.getWinHandle(999))
        .to.be.revertedWithCustomError(diceGame, "GameNotFound");
    });

    it("Should reject finalize without request", async function () {
      const fakeProof = "0x";
      await expect(diceGame.finalizeResultReveal(0, true, fakeProof))
        .to.be.revertedWithCustomError(diceGame, "GameNotRevealed");
    });
  });

  describe("Claiming Winnings", function () {
    beforeEach(async function () {
      await diceGame.connect(player1).placeBet(0, 7, { value: MIN_BET });
    });

    it("Should reject claim before reveal", async function () {
      await expect(diceGame.connect(player1).claimWinnings(0))
        .to.be.revertedWithCustomError(diceGame, "GameNotRevealed");
    });

    it("Should reject claim from non-player", async function () {
      await expect(diceGame.connect(player2).claimWinnings(0))
        .to.be.revertedWithCustomError(diceGame, "NotGamePlayer");
    });
  });

  describe("House Funding", function () {
    it("Should accept house funds", async function () {
      const fundAmount = ethers.parseEther("50");
      await diceGame.fundHouse({ value: fundAmount });

      const balance = await diceGame.getHouseBalance();
      expect(balance).to.be.gte(fundAmount);
    });
  });

  describe("Public Decryption Flow", function () {
    it("Documents the complete 3-step async decryption flow", async function () {
      /**
       * Complete Public Decryption Flow for Dice Game:
       *
       * STEP 1 - On-chain: Place Bet & Request Reveal
       * ==============================================
       * const gameId = await diceGame.placeBet(BetType.OverUnder, 7, { value: betAmount });
       * await diceGame.requestResultReveal(gameId);
       * // Internally calls: FHE.makePubliclyDecryptable(game.won)
       * // Emits: ResultReadyForReveal event
       *
       * STEP 2 - Off-chain: Decrypt via Relayer SDK
       * ============================================
       * const handle = await diceGame.getWinHandle(gameId);
       * const result = await fhevmInstance.publicDecrypt([handle]);
       * const won = result.clearValues[handle];  // true or false
       * const proof = result.decryptionProof;
       *
       * STEP 3 - On-chain: Finalize with Proof
       * ======================================
       * await diceGame.finalizeResultReveal(gameId, won, proof);
       * // Internally calls: FHE.checkSignatures(cts, cleartexts, proof)
       * // Calculates payout if won
       * // Emits: ResultRevealed(gameId, player, won)
       *
       * CLAIM WINNINGS
       * ==============
       * await diceGame.claimWinnings(gameId);
       * // Transfers payout to player if they won
       * // Emits: WinningsClaimed(gameId, player, amount)
       */
    });
  });

  describe("Fairness", function () {
    it("Should use FHE.random for dice rolls", async function () {
      /**
       * FHE guarantees:
       * - Dice values generated with FHE.randEuint64()
       * - Values mapped to 1-6 range with modulo
       * - No one can predict or manipulate rolls
       * - Results encrypted until explicit reveal via public decryption
       */
    });

    it("Should have mathematically fair payouts", async function () {
      /**
       * Probability analysis:
       * - Over/Under 7: ~58% win rate, 1.95x payout = 1.13% house edge
       * - Exact: ~2.78% win rate, 5x payout = 86.1% house edge
       * - High/Low: ~41.67% win rate, 1.9x payout = 20.8% house edge
       */
    });
  });

  describe("Security", function () {
    it("Should verify decryption proofs", async function () {
      /**
       * Security guarantees:
       * - FHE.checkSignatures verifies Zama KMS proof
       * - Cannot submit fake win/loss result
       * - Proof cryptographically bound to specific ciphertext
       * - Transaction reverts if proof is invalid or tampered
       */
    });
  });
});
