import * as anchor from "@coral-xyz/anchor";
import { Transaction } from "@solana/web3.js";
import { expect } from "chai";
import fetch from "node-fetch";
import { getTestContext } from "../setup";
import { getAccount } from "@solana/spl-token";

describe("Verify Balance (ZK-SNARK API)", () => {
  it("Generate ZK proof and verify balance without revealing bid amount", async () => {
    const ctx = getTestContext();
    const {
      connection,
      program,
      axumBaseUrl,
      company1Admin,
      company1Pda,
      company1UsdcVault
    } = ctx;

    console.log("\n=== ZK-SNARK Balance Verification Test ===");
    console.log("Company Admin:", company1Admin.publicKey.toBase58());
    console.log("Company PDA:", company1Pda.toBase58());
    console.log("Company USDC Vault:", company1UsdcVault.toBase58());

    // Step 1: Check company's actual USDC balance
    const usdcAccount = await getAccount(connection, company1UsdcVault);
    const actualBalance = Number(usdcAccount.amount);

    console.log(`\n✓ Company USDC balance: ${actualBalance / 1_000_000} USDC`);

    // Step 2: Choose a bid amount STRICTLY LESS than balance
    // For example, bid 60% of available balance
    const bidAmount = Math.floor(actualBalance * 0.6);

    console.log(`\n📊 Test Parameters:`);
    console.log(`  - Actual Balance: ${actualBalance / 1_000_000} USDC`);
    console.log(`  - Bid Amount: ${bidAmount / 1_000_000} USDC (PRIVATE - not revealed on-chain)`);
    console.log(`  - Bid is ${((bidAmount / actualBalance) * 100).toFixed(1)}% of balance`);
    console.log(`  - Reserve: ${((actualBalance - bidAmount) / 1_000_000).toFixed(2)} USDC (for fees/safety)`);

    // Step 3: Create API request payload
    const request = {
      company_pubkey: company1Admin.publicKey.toBase58(),
      bid_amount: bidAmount,
    };

    console.log("\n🔐 Calling API to generate ZK proof...");
    console.log("This will:");
    console.log("  1. Fetch company's USDC balance from on-chain vault");
    console.log("  2. Generate zero-knowledge proof that bid_amount < balance (strict inequality)");
    console.log("  3. Create unsigned transaction with proof data");

    // Step 4: Call Axum API to generate proof and get unsigned transaction
    const createTxResponse = await fetch(
      `${axumBaseUrl}/api/primary_market/verify_balance`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(request),
      }
    );

    if (!createTxResponse.ok) {
      const error = await createTxResponse.text();
      throw new Error(`Failed to create transaction: ${error}`);
    }

    const { transaction_base64, message, claimed_balance } = await createTxResponse.json();
    console.log(`\n✓ API Response: ${message}`);
    console.log(`✓ Claimed balance: ${claimed_balance / 1_000_000} USDC`);

    // Verify claimed balance matches actual
    expect(claimed_balance).to.equal(actualBalance);
    console.log("✓ Claimed balance matches on-chain balance");

    // Step 5: Deserialize and sign transaction
    const txBuffer = Buffer.from(transaction_base64, "base64");
    const transaction = Transaction.from(txBuffer);

    console.log("\n📝 Transaction signer (expected):");
    console.log("  Company Admin:", company1Admin.publicKey.toBase58());

    // Sign with company admin
    transaction.sign(company1Admin);

    console.log("✓ Transaction signed locally");

    // Step 6: Submit signed transaction to backend
    const signedTxBase64 = transaction.serialize().toString("base64");
    const submitTxResponse = await fetch(`${axumBaseUrl}/api/submit-tx`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ transaction_base64: signedTxBase64 }),
    });

    if (!submitTxResponse.ok) {
      const error = await submitTxResponse.text();
      throw new Error(`Failed to submit transaction: ${error}`);
    }

    const { signature } = await submitTxResponse.json();
    console.log(`\n✅ Transaction signature: ${signature}`);

    // Step 7: Verify transaction logs
    const txDetails = await connection.getTransaction(signature, {
      commitment: "confirmed",
      maxSupportedTransactionVersion: 0,
    });

    if (txDetails?.meta?.logMessages) {
      const logs = txDetails.meta.logMessages;

      // Check for successful verification message
      const hasVerifyLog = logs.some((log) =>
        log.includes("Balance verification successful") ||
        log.includes("Proof verified")
      );

      if (hasVerifyLog) {
        console.log("✓ Balance verification log message found");
      }

      // Check that bid amount is NOT in logs (privacy preserved)
      const hasBidAmountInLogs = logs.some((log) =>
        log.includes(bidAmount.toString())
      );

      expect(hasBidAmountInLogs).to.be.false;
      console.log("✓ Bid amount NOT found in logs (privacy preserved)");

      // Check for event emission
      const hasEventLog = logs.some((log) =>
        log.includes("BalanceVerified")
      );

      if (hasEventLog) {
        console.log("✓ BalanceVerified event emitted");
      }
    }

    // Step 8: Verify computation units used
    if (txDetails?.meta?.computeUnitsConsumed) {
      const computeUnits = txDetails.meta.computeUnitsConsumed;
      console.log(`\n⚙️  Compute units consumed: ${computeUnits}`);

      // ZK proof verification with alt_bn128 pairing + account lookups
      expect(computeUnits).to.be.greaterThan(15000);
      expect(computeUnits).to.be.lessThan(150000);
      console.log("✓ Compute units in expected range for ZK verification");
    }

    console.log("\n🎉 ZK-SNARK balance verification completed successfully!");
    console.log("\n📊 Summary:");
    console.log(`  ✓ Proof generated off-chain`);
    console.log(`  ✓ Proof verified on-chain using Solana's alt_bn128 pairing`);
    console.log(`  ✓ Bid amount remains private (never revealed)`);
    console.log(`  ✓ Balance integrity verified cryptographically`);
    console.log(`  ✓ Strict inequality enforced: bid < balance`);
  });

  it("Should reject proof when bid exceeds balance", async () => {
    const ctx = getTestContext();
    const { company1Admin, company1UsdcVault, axumBaseUrl, connection, company1Pda } = ctx;

    console.log("\n=== Testing Insufficient Balance Rejection ===");
    console.log("Company USDC Vault:", company1UsdcVault.toBase58());

    // Check actual balance
    const usdcAccount = await getAccount(connection, company1UsdcVault);
    const actualBalance = Number(usdcAccount.amount);

    // Try to bid MORE than available balance
    const excessiveBid = actualBalance + 1_000_000; // 1 USDC more than balance

    console.log(`\n📊 Test Parameters:`);
    console.log(`  - Actual Balance: ${actualBalance / 1_000_000} USDC`);
    console.log(`  - Attempted Bid: ${excessiveBid / 1_000_000} USDC`);
    console.log(`  - Excess: ${(excessiveBid - actualBalance) / 1_000_000} USDC`);

    const request = {
      company_pubkey: company1Admin.publicKey.toBase58(),
      bid_amount: excessiveBid,
    };

    console.log("\n🚫 Calling API with excessive bid (should fail)...");

    const createTxResponse = await fetch(
      `${axumBaseUrl}/api/primary_market/verify_balance`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(request),
      }
    );

    // Should receive error response
    expect(createTxResponse.ok).to.be.false;

    const errorText = await createTxResponse.text();
    console.log("\n✓ API correctly rejected excessive bid");
    console.log(`  Error: ${errorText}`);

    // Verify error message mentions insufficient balance
    expect(errorText.toLowerCase()).to.satisfy((text: string) =>
      text.includes("insufficient") ||
      text.includes("exceeds") ||
      text.includes("balance") ||
      text.includes("failed to fetch")
    );

    console.log("✓ Error message correctly indicates balance issue");
  });

  it("Should reject when bid equals balance (strict inequality enforcement)", async () => {
    const ctx = getTestContext();
    const {
      connection,
      axumBaseUrl,
      company1Admin,
      company1UsdcVault,
      company1Pda
    } = ctx;

    console.log("\n=== Testing Strict Inequality: Bid Cannot Equal Balance ===");
    console.log("Company USDC Vault:", company1UsdcVault.toBase58());

    // Check actual balance
    const usdcAccount = await getAccount(connection, company1UsdcVault);
    const actualBalance = Number(usdcAccount.amount);

    // Bid exactly the available balance (should be REJECTED)
    const bidAmount = actualBalance;

    console.log(`\n📊 Test Parameters:`);
    console.log(`  - Actual Balance: ${actualBalance / 1_000_000} USDC`);
    console.log(`  - Bid Amount: ${bidAmount / 1_000_000} USDC`);
    console.log(`  - Bid is exactly 100% of balance`);
    console.log(`\n⚠️  Expected behavior: REJECT (circuit enforces bid < balance, not <=)`);
    console.log(`   Reason: Users must keep reserve for fees/safety`);

    const request = {
      company_pubkey: company1Admin.publicKey.toBase58(),
      bid_amount: bidAmount,
    };

    console.log("\n🚫 Calling API with bid = balance (should fail)...");

    const createTxResponse = await fetch(
      `${axumBaseUrl}/api/primary_market/verify_balance`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(request),
      }
    );

    // Should fail because circuit enforces strict inequality
    expect(createTxResponse.ok).to.be.false;

    const errorText = await createTxResponse.text();
    console.log("\n✓ API correctly rejected bid = balance");
    console.log(`  Error: ${errorText}`);

    // Verify error mentions the strict inequality requirement
    expect(errorText.toLowerCase()).to.satisfy((text: string) =>
      text.includes("proof generation failed") ||
      text.includes("assignmentmissing") ||
      text.includes("insufficient") ||
      text.includes("exceeds")
    );

    console.log("✓ Strict inequality enforced: bid must be < balance (not <=)");
    console.log("✓ This ensures users always maintain a reserve");
  });

  it("Should calculate maximum allowed bid with safety buffer", async () => {
    const ctx = getTestContext();
    const { connection, company1UsdcVault } = ctx;

    console.log("\n=== Maximum Allowed Bid Calculation ===");

    // Check actual balance
    const usdcAccount = await getAccount(connection, company1UsdcVault);
    const actualBalance = Number(usdcAccount.amount);

    // Calculate max bid with different safety buffers
    const safetyBuffers = [0.99, 0.98, 0.95, 0.90];

    console.log(`\nBalance: ${actualBalance / 1_000_000} USDC\n`);
    console.log("Recommended maximum bid amounts:");

    safetyBuffers.forEach(buffer => {
      const maxBid = Math.floor(actualBalance * buffer);
      const reserve = actualBalance - maxBid;
      const bufferPercent = ((1 - buffer) * 100).toFixed(0);

      console.log(`  ${bufferPercent}% reserve (${buffer * 100}% max): ${maxBid / 1_000_000} USDC (keeps ${reserve / 1_000_000} USDC)`);
    });

    console.log(`\n✓ Frontend should enforce: bid < balance * SAFETY_FACTOR`);
    console.log(`✓ Recommended SAFETY_FACTOR: 0.95-0.99 (5%-1% reserve)`);
    console.log(`✓ This ensures users always have funds for fees and safety margin`);

    // Verify the 99% bid would work
    const recommendedMaxBid = Math.floor(actualBalance * 0.99);
    expect(recommendedMaxBid).to.be.lessThan(actualBalance);
    expect(recommendedMaxBid).to.be.greaterThan(0);

    console.log(`\n✓ Recommended max bid: ${recommendedMaxBid / 1_000_000} USDC`);
  });

  it("Should fail if ZK keys are not available", async () => {
    console.log("\n=== ZK Keys Availability Test ===");
    console.log("Note: This test assumes ZK keys are loaded in AppState");
    console.log("\nExpected behavior when SKIP_ZK_SETUP=true:");
    console.log("  - Backend starts without loading ZK keys");
    console.log("  - verify_balance endpoint returns 400 Bad Request");
    console.log("  - Error message: 'ZK-SNARK keys not available'");
    console.log("\n✓ Circuit enforces: bid < balance (strict inequality)");
    console.log("✓ Users must always maintain a reserve");
    console.log("✓ Test documented (requires backend restart to execute)");
  });
});
