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

describe("Place and Cancel Bid (API)", () => {
  it("Company 1 (BMW) Places Bid for 500 EUA", async () => {
    const ctx = getTestContext();
    const {
      connection,
      program,
      axumBaseUrl,
      company1Admin,
      company1Pda,
      company1UsdcVault,
      auctionPda,
    } = ctx;

    console.log("=== Company 1 Placing Bid ===");

    // Derive primary market and verifying key PDAs
    const [primaryMarketPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [Buffer.from("primary_market")],
      program.programId
    );

    const [verifyingKeyPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [Buffer.from("verifying_key")],
      program.programId
    );

    // Step 1: Get company's USDC balance
    const vaultAccount = await getAccount(connection, company1UsdcVault);
    const companyBalance = Number(vaultAccount.amount);
    console.log(`Company 1 USDC balance: ${companyBalance / 1_000_000} USDC`);

    // Step 2: Prepare bid parameters
    const euaVolume = 500; // Must be divisible by 250
    const bidAmount = 25_000_000; // 25 USDC (6 decimals) - PRIVATE
    const nonce = crypto.randomBytes(16).toString("hex");
    const salt = crypto.randomBytes(32).toString("hex");

    // Verify sufficient balance
    if (bidAmount > companyBalance) {
      throw new Error(`Insufficient balance: ${companyBalance} < ${bidAmount}`);
    }

    // Step 3: Get auction info for bid_id calculation
    const auction = await program.account.auction.fetch(auctionPda);
    const bidId = auction.totalBids;

    // Derive bid record PDA
    const [bidRecordPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [
        Buffer.from("bid"),
        auctionPda.toBuffer(),
        company1Pda.toBuffer(),
        new anchor.BN(bidId).toArrayLike(Buffer, "le", 8),
      ],
      program.programId
    );

    console.log(`Bid Record PDA: ${bidRecordPda.toBase58()}`);
    console.log(`Bid ID: ${bidId}`);
    console.log(`EUA Volume: ${euaVolume}`);
    console.log(`Bid Amount: [PRIVATE - ${bidAmount / 1_000_000} USDC]`);

    // Step 4: Create API request payload
    const request = {
      primary_market: primaryMarketPda.toBase58(),
      verifying_key: verifyingKeyPda.toBase58(),
      auction: auctionPda.toBase58(),
      company: company1Pda.toBase58(),
      company_usdc_vault: company1UsdcVault.toBase58(),
      bid_record: bidRecordPda.toBase58(),
      company_admin: company1Admin.publicKey.toBase58(),
      bid_amount: bidAmount, // Backend will generate proof
      eua_volume: euaVolume,
      nonce: nonce,
      salt: salt,
    };

    console.log("Calling API to create place bid transaction...");
    console.log("⚠️  This will generate a ZK-SNARK proof on the backend");

    // Step 5: Call Axum API to get unsigned transaction
    const createTxResponse = await fetch(
      `${axumBaseUrl}/api/primary-market/place-bid`,
      {
        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, bid_hash } = await createTxResponse.json();
    console.log(`API Response: ${message}`);
    console.log(`Bid Hash: ${bid_hash}`);

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

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

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

    console.log("Transaction signed locally");

    // Step 7: 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(`✓ Transaction signature: ${signature}`);

    // Step 8: Verify on-chain state - BID PLACED
    const bidRecord = await program.account.bidRecord.fetch(bidRecordPda);

    expect(bidRecord.company.equals(company1Pda)).to.be.true;
    expect(bidRecord.auction.equals(auctionPda)).to.be.true;
    expect(bidRecord.euaVolume.toNumber()).to.equal(euaVolume);
    expect(bidRecord.bidId.toNumber()).to.equal(bidId);
    expect(bidRecord.isCancelled).to.be.false;
    expect(bidRecord.bidHash).to.deep.equal(Buffer.from(bid_hash, "hex"));

    // Important: bid_amount is NOT stored on-chain (privacy!)
    console.log("✓ Bid record verified");
    console.log(`  - Bid ID: ${bidRecord.bidId.toNumber()}`);
    console.log(`  - EUA Volume: ${bidRecord.euaVolume.toNumber()}`);
    console.log(`  - Bid Amount: [NOT STORED ON-CHAIN - remains private]`);
    console.log(`  - Bid Hash: ${bid_hash}`);

    // Step 9: Verify company state updated
    const company = await program.account.company.fetch(company1Pda);
    expect(company.placedBid).to.be.true;

    console.log("✓ Company 1 state verified");
    console.log(`  - Placed Bid: ${company.placedBid}`);

    // Step 10: Verify auction state updated
    const auctionAfter = await program.account.auction.fetch(auctionPda);
    expect(auctionAfter.totalBids).to.equal(bidId + 1);

    console.log("✓ Auction state verified");
    console.log(`  - Total Bids: ${auctionAfter.totalBids}`);

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

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

      const hasBidLog = logs.some((log) =>
        log.includes("Bid placed")
      );

      if (hasBidLog) {
        console.log("✓ Bid placed log message found");
      }

      const hasEventLog = logs.some((log) =>
        log.includes("BidPlaced")
      );

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

    console.log("✓ Company 1 placed bid successfully via API");

    // Store for cancel test
    setTestContext({
      company1BidRecordPda: bidRecordPda,
      company1BidId: bidId,
    });
  });

  it("Company 2 (Total) Places Bid for 250 EUA", async () => {
    const ctx = getTestContext();
    const {
      connection,
      program,
      axumBaseUrl,
      company2Admin,
      company2Pda,
      company2UsdcVault,
      auctionPda,
    } = ctx;

    console.log("=== Company 2 Placing Bid ===");

    const [primaryMarketPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [Buffer.from("primary_market")],
      program.programId
    );

    const [verifyingKeyPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [Buffer.from("verifying_key")],
      program.programId
    );

    // Get balance
    const vaultAccount = await getAccount(connection, company2UsdcVault);
    const companyBalance = Number(vaultAccount.amount);
    console.log(`Company 2 USDC balance: ${companyBalance / 1_000_000} USDC`);

    // Bid parameters
    const euaVolume = 250;
    const bidAmount = 18_000_000; // 18 USDC - PRIVATE
    const nonce = crypto.randomBytes(16).toString("hex");
    const salt = crypto.randomBytes(32).toString("hex");

    // Get bid_id
    const auction = await program.account.auction.fetch(auctionPda);
    const bidId = auction.totalBids;

    const [bidRecordPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [
        Buffer.from("bid"),
        auctionPda.toBuffer(),
        company2Pda.toBuffer(),
        new anchor.BN(bidId).toArrayLike(Buffer, "le", 8),
      ],
      program.programId
    );

    const request = {
      primary_market: primaryMarketPda.toBase58(),
      verifying_key: verifyingKeyPda.toBase58(),
      auction: auctionPda.toBase58(),
      company: company2Pda.toBase58(),
      company_usdc_vault: company2UsdcVault.toBase58(),
      bid_record: bidRecordPda.toBase58(),
      company_admin: company2Admin.publicKey.toBase58(),
      bid_amount: bidAmount,
      eua_volume: euaVolume,
      nonce: nonce,
      salt: salt,
    };

    console.log("Calling API to create place bid transaction...");

    const createTxResponse = await fetch(
      `${axumBaseUrl}/api/primary-market/place-bid`,
      {
        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, bid_hash } = await createTxResponse.json();

    const txBuffer = Buffer.from(transaction_base64, "base64");
    const transaction = Transaction.from(txBuffer);
    transaction.sign(company2Admin);

    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(`✓ Transaction signature: ${signature}`);

    // Verify bid record
    const bidRecord = await program.account.bidRecord.fetch(bidRecordPda);
    expect(bidRecord.euaVolume.toNumber()).to.equal(euaVolume);

    console.log("✓ Company 2 placed bid successfully");
    console.log(`  - EUA Volume: ${euaVolume}`);
    console.log(`  - Bid Hash: ${bid_hash}`);

    // Store for cancel test
    setTestContext({
      company2BidRecordPda: bidRecordPda,
      company2BidId: bidId,
    });
  });

  it("Should Reject Bid with Insufficient Balance", async () => {
    const ctx = getTestContext();
    const {
      axumBaseUrl,
      company1Admin,
      company1Pda,
      company1UsdcVault,
      auctionPda,
      program,
    } = ctx;

    const [primaryMarketPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [Buffer.from("primary_market")],
      program.programId
    );

    const [verifyingKeyPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [Buffer.from("verifying_key")],
      program.programId
    );

    // Get current balance
    const vaultAccount = await getAccount(ctx.connection, company1UsdcVault);
    const companyBalance = Number(vaultAccount.amount);

    // Try to bid more than balance
    const excessiveBidAmount = companyBalance + 1_000_000;

    const auction = await program.account.auction.fetch(auctionPda);
    const bidId = auction.totalBids;

    const [bidRecordPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [
        Buffer.from("bid"),
        auctionPda.toBuffer(),
        company1Pda.toBuffer(),
        new anchor.BN(bidId).toArrayLike(Buffer, "le", 8),
      ],
      program.programId
    );

    const request = {
      primary_market: primaryMarketPda.toBase58(),
      verifying_key: verifyingKeyPda.toBase58(),
      auction: auctionPda.toBase58(),
      company: company1Pda.toBase58(),
      company_usdc_vault: company1UsdcVault.toBase58(),
      bid_record: bidRecordPda.toBase58(),
      company_admin: company1Admin.publicKey.toBase58(),
      bid_amount: excessiveBidAmount,
      eua_volume: 250,
      nonce: crypto.randomBytes(16).toString("hex"),
      salt: crypto.randomBytes(32).toString("hex"),
    };

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

    expect(createTxResponse.ok).to.be.false;
    const errorText = await createTxResponse.text();
    console.log("✓ Correctly rejected insufficient balance:", errorText);
  });

  it("Should Reject Bid with Invalid EUA Volume", async () => {
    const ctx = getTestContext();
    const {
      axumBaseUrl,
      company1Admin,
      company1Pda,
      company1UsdcVault,
      auctionPda,
      program,
    } = ctx;

    const [primaryMarketPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [Buffer.from("primary_market")],
      program.programId
    );

    const [verifyingKeyPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [Buffer.from("verifying_key")],
      program.programId
    );

    const auction = await program.account.auction.fetch(auctionPda);
    const bidId = auction.totalBids;

    const [bidRecordPda] = anchor.web3.PublicKey.findProgramAddressSync(
      [
        Buffer.from("bid"),
        auctionPda.toBuffer(),
        company1Pda.toBuffer(),
        new anchor.BN(bidId).toArrayLike(Buffer, "le", 8),
      ],
      program.programId
    );

    const request = {
      primary_market: primaryMarketPda.toBase58(),
      verifying_key: verifyingKeyPda.toBase58(),
      auction: auctionPda.toBase58(),
      company: company1Pda.toBase58(),
      company_usdc_vault: company1UsdcVault.toBase58(),
      bid_record: bidRecordPda.toBase58(),
      company_admin: company1Admin.publicKey.toBase58(),
      bid_amount: 10_000_000,
      eua_volume: 123, // Not divisible by 250
      nonce: crypto.randomBytes(16).toString("hex"),
      salt: crypto.randomBytes(32).toString("hex"),
    };

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

    expect(createTxResponse.ok).to.be.false;
    const errorText = await createTxResponse.text();
    console.log("✓ Correctly rejected invalid EUA volume:", errorText);
  });

  it("Company 1 Cancels Their Bid", async () => {
    const ctx = getTestContext();
    const {
      connection,
      program,
      axumBaseUrl,
      company1Admin,
      company1Pda,
      auctionPda,
      company1BidRecordPda,
      company1BidId,
    } = ctx;

    console.log("=== Company 1 Cancelling Bid ===");

    // Verify bid exists and is not cancelled
    const bidBefore = await program.account.bidRecord.fetch(company1BidRecordPda);
    expect(bidBefore.isCancelled).to.be.false;

    const request = {
      auction: auctionPda.toBase58(),
      company: company1Pda.toBase58(),
      bid_record: company1BidRecordPda.toBase58(),
      company_admin: company1Admin.publicKey.toBase58(),
    };

    console.log("Calling API to create cancel bid transaction...");

    const createTxResponse = await fetch(
      `${axumBaseUrl}/api/primary-market/cancel-bid`,
      {
        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 } = await createTxResponse.json();
    console.log(`API Response: ${message}`);

    const txBuffer = Buffer.from(transaction_base64, "base64");
    const transaction = Transaction.from(txBuffer);
    transaction.sign(company1Admin);

    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(`✓ Transaction signature: ${signature}`);

    // Verify bid cancelled
    const bidAfter = await program.account.bidRecord.fetch(company1BidRecordPda);
    expect(bidAfter.isCancelled).to.be.true;

    console.log("✓ Bid cancelled successfully");

    // Verify company state
    const company = await program.account.company.fetch(company1Pda);
    expect(company.placedBid).to.be.false;

    console.log("✓ Company 1 state updated (placed_bid = false)");

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

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

      const hasCancelLog = logs.some((log) =>
        log.includes("Bid cancelled")
      );

      if (hasCancelLog) {
        console.log("✓ Bid cancelled log message found");
      }
    }
  });

  it("Verify Final Auction State", async () => {
    const ctx = getTestContext();
    const { program, auctionPda } = ctx;

    const auction = await program.account.auction.fetch(auctionPda);

    console.log("\n=== Final Auction State ===");
    console.log(`Total Bids Placed: ${auction.totalBids}`);
    console.log(`Active Bids: ${auction.totalBids - 1}`); // One cancelled
    console.log(`Status: Active`);
    console.log(`EUA Volume: ${auction.euaVolume.toNumber()}`);

    // We should have 2 total bids, 1 cancelled
    expect(auction.totalBids).to.equal(2);
    expect(auction.status).to.deep.equal({ active: {} });

    console.log("✓ All bid operations verified successfully");
  });
});
