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

describe("Allocate EUA Tokens (API)", () => {
  it("Allocate EUA Tokens to Companies via Backend API", async () => {
    const ctx = getTestContext();
    const {
      connection,
      program,
      axumBaseUrl,
      registryPda,
      company1Pda,
      company2Pda,
      globalAdmin,
      euaMint,
      company1EuaVault,
      company2EuaVault,
      db,
    } = ctx;

    // ============================================================
    // STEP 1: Check DB State Before
    // ============================================================
    console.log("Checking database state before EUA allocation...");

    // Check initial balances
    const bmwBefore = await db.getCompany(company1Pda.toBase58());
    const totalBefore = await db.getCompany(company2Pda.toBase58());

    expect(bmwBefore.eua_balance).to.equal("0");
    expect(totalBefore.eua_balance).to.equal("0");

    // Check minting logs (should be empty)
    const bmwMintLogsBefore = await db.getEuaMintingLogs(company1Pda.toBase58());
    const totalMintLogsBefore = await db.getEuaMintingLogs(company2Pda.toBase58());

    const bmwMintedBefore = await db.getTotalEuaMinted(company1Pda.toBase58());
    const totalMintedBefore = await db.getTotalEuaMinted(company2Pda.toBase58());

    console.log(`✓ BMW initial EUA balance: ${bmwBefore.eua_balance}`);
    console.log(`✓ Total initial EUA balance: ${totalBefore.eua_balance}`);
    console.log(`✓ BMW minting logs: ${bmwMintLogsBefore.length}`);
    console.log(`✓ Total minting logs: ${totalMintLogsBefore.length}`);

    // ============================================================
    // STEP 2: Allocate 1000 EUA to Company 1 (BMW)
    // ============================================================
    console.log("Allocating 1000 EUA tokens to BMW...");

    const bmwEuaAmount = 1000;
    const bmwRequest = {
      registry: registryPda.toBase58(),
      company: company1Pda.toBase58(),
      eua_mint: euaMint.toBase58(),
      eua_vault: company1EuaVault.toBase58(),
      global_admin: globalAdmin.publicKey.toBase58(),
      token_program: TOKEN_PROGRAM_ID.toBase58(),
      amount: bmwEuaAmount,
    };

    const createBmwTxResponse = await fetch(
      `${axumBaseUrl}/api/registry/mint-eua`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(bmwRequest),
      }
    );

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

    const { transaction_base64: bmwTxBase64 } =
      await createBmwTxResponse.json();

    const bmwTxBuffer = Buffer.from(bmwTxBase64, "base64");
    const bmwTransaction = Transaction.from(bmwTxBuffer);
    bmwTransaction.sign(globalAdmin);

    const signedBmwTxBase64 = bmwTransaction.serialize().toString("base64");
    const submitBmwTxResponse = await fetch(`${axumBaseUrl}/api/submit-tx`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ transaction_base64: signedBmwTxBase64 }),
    });

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

    const { signature: bmwSignature } = await submitBmwTxResponse.json();
    console.log(`✓ BMW EUA mint transaction signature: ${bmwSignature}`);

    // ============================================================
    // STEP 3: Allocate 500 EUA to Company 2 (Total Energies)
    // ============================================================
    console.log("Allocating 500 EUA tokens to Total Energies...");

    const totalEuaAmount = 500;
    const totalRequest = {
      registry: registryPda.toBase58(),
      company: company2Pda.toBase58(),
      eua_mint: euaMint.toBase58(),
      eua_vault: company2EuaVault.toBase58(),
      global_admin: globalAdmin.publicKey.toBase58(),
      token_program: TOKEN_PROGRAM_ID.toBase58(),
      amount: totalEuaAmount,
    };

    const createTotalTxResponse = await fetch(
      `${axumBaseUrl}/api/registry/mint-eua`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(totalRequest),
      }
    );

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

    const { transaction_base64: totalTxBase64 } =
      await createTotalTxResponse.json();

    const totalTxBuffer = Buffer.from(totalTxBase64, "base64");
    const totalTransaction = Transaction.from(totalTxBuffer);
    totalTransaction.sign(globalAdmin);

    const signedTotalTxBase64 = totalTransaction.serialize().toString("base64");
    const submitTotalTxResponse = await fetch(`${axumBaseUrl}/api/submit-tx`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ transaction_base64: signedTotalTxBase64 }),
    });

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

    const { signature: totalSignature } = await submitTotalTxResponse.json();
    console.log(`✓ Total EUA mint transaction signature: ${totalSignature}`);

    // ============================================================
    // STEP 4: Verify EUA Token Balances On-Chain
    // ============================================================
    console.log("Verifying EUA token balances on-chain...");

    const bmwEuaAccount = await getAccount(connection, company1EuaVault);
    expect(bmwEuaAccount.amount.toString()).to.equal(bmwEuaAmount.toString());
    console.log(`✓ BMW EUA vault balance: ${bmwEuaAccount.amount} EUA`);

    const totalEuaAccount = await getAccount(connection, company2EuaVault);
    expect(totalEuaAccount.amount.toString()).to.equal(totalEuaAmount.toString());
    console.log(`✓ Total EUA vault balance: ${totalEuaAccount.amount} EUA`);

    // ============================================================
    // STEP 5: Verify Token Account Ownership
    // ============================================================
    console.log("Verifying token account ownership...");

    expect(bmwEuaAccount.owner.equals(company1Pda)).to.be.true;
    console.log(`✓ BMW EUA vault owned by: ${bmwEuaAccount.owner.toBase58()}`);

    expect(totalEuaAccount.owner.equals(company2Pda)).to.be.true;
    console.log(
      `✓ Total EUA vault owned by: ${totalEuaAccount.owner.toBase58()}`
    );

    // ============================================================
    // STEP 6: Verify Token Mint
    // ============================================================
    console.log("Verifying token mint addresses...");

    expect(bmwEuaAccount.mint.equals(euaMint)).to.be.true;
    expect(totalEuaAccount.mint.equals(euaMint)).to.be.true;
    console.log(`✓ Both vaults hold tokens from EUA mint: ${euaMint.toBase58()}`);

    // ============================================================
    // STEP 7: Verify Company Account State (Unchanged)
    // ============================================================
    console.log("Verifying company account states...");

    const company1 = await program.account.company.fetch(company1Pda);
    expect(company1.euaVault.equals(company1EuaVault)).to.be.true;
    expect(company1.isActive).to.be.true;
    console.log(`✓ BMW company account verified: ${company1.name}`);

    const company2 = await program.account.company.fetch(company2Pda);
    expect(company2.euaVault.equals(company2EuaVault)).to.be.true;
    expect(company2.isActive).to.be.true;
    console.log(`✓ Total company account verified: ${company2.name}`);

    // ============================================================
    // STEP 8: Verify DB State After (Wait for Event Handler)
    // ============================================================
    console.log("Verifying database state after EUA allocation...");

    // Wait for EUA minting to be logged
    const bmwMintingLogged = await db.waitForEuaMintingLog(
      company1Pda.toBase58(),
      BigInt(bmwEuaAmount),
      5000
    );
    expect(bmwMintingLogged).to.be.true;

    const totalMintingLogged = await db.waitForEuaMintingLog(
      company2Pda.toBase58(),
      BigInt(totalEuaAmount),
      5000
    );
    expect(totalMintingLogged).to.be.true;

    // Verify BMW EUA balance in DB
    const bmwAfter = await db.getCompany(company1Pda.toBase58());
    expect(bmwAfter.eua_balance).to.equal(bmwEuaAmount.toString());
    console.log(`✓ BMW EUA balance in DB: ${bmwAfter.eua_balance}`);

    // Verify Total EUA balance in DB
    const totalAfter = await db.getCompany(company2Pda.toBase58());
    expect(totalAfter.eua_balance).to.equal(totalEuaAmount.toString());
    console.log(`✓ Total EUA balance in DB: ${totalAfter.eua_balance}`);

    // Verify minting logs
    const bmwMintLogsAfter = await db.getEuaMintingLogs(company1Pda.toBase58());
    expect(bmwMintLogsAfter.length).to.be.greaterThan(0);

    const bmwLatestLog = bmwMintLogsAfter[0];
    expect(bmwLatestLog.company_pubkey).to.equal(company1Pda.toBase58());
    expect(bmwLatestLog.company_name).to.equal("BMW Manufacturing GmbH");
    expect(bmwLatestLog.amount).to.equal(bmwEuaAmount.toString());
    expect(bmwLatestLog.minted_by).to.equal(globalAdmin.publicKey.toBase58());
    expect(bmwLatestLog.eua_mint).to.equal(euaMint.toBase58());
    expect(bmwLatestLog.eua_vault).to.equal(company1EuaVault.toBase58());
    console.log(`✓ BMW minting log verified (${bmwMintLogsAfter.length} total logs)`);

    const totalMintLogsAfter = await db.getEuaMintingLogs(company2Pda.toBase58());
    expect(totalMintLogsAfter.length).to.be.greaterThan(0);

    const totalLatestLog = totalMintLogsAfter[0];
    expect(totalLatestLog.company_pubkey).to.equal(company2Pda.toBase58());
    expect(totalLatestLog.company_name).to.equal("Total Energies SA");
    expect(totalLatestLog.amount).to.equal(totalEuaAmount.toString());
    expect(totalLatestLog.minted_by).to.equal(globalAdmin.publicKey.toBase58());
    expect(totalLatestLog.eua_mint).to.equal(euaMint.toBase58());
    expect(totalLatestLog.eua_vault).to.equal(company2EuaVault.toBase58());
    console.log(`✓ Total minting log verified (${totalMintLogsAfter.length} total logs)`);

    // Verify total minted amounts
    const bmwTotalMinted = await db.getTotalEuaMinted(company1Pda.toBase58());
    const totalTotalMinted = await db.getTotalEuaMinted(company2Pda.toBase58());

    expect(bmwTotalMinted).to.equal(BigInt(bmwEuaAmount));
    expect(totalTotalMinted).to.equal(BigInt(totalEuaAmount));
    console.log(`✓ BMW total minted: ${bmwTotalMinted} EUA`);
    console.log(`✓ Total total minted: ${totalTotalMinted} EUA`);

    // Verify global total
    const globalTotalMinted = await db.getTotalEuaMinted();
    expect(globalTotalMinted).to.equal(BigInt(bmwEuaAmount + totalEuaAmount));
    console.log(`✓ Global total minted: ${globalTotalMinted} EUA`);

    // ============================================================
    // STEP 9: Summary
    // ============================================================
    console.log("\n=== EUA Allocation Summary ===");
    console.log(`BMW Manufacturing GmbH:`);
    console.log(`  - On-Chain: ${bmwEuaAccount.amount} EUA`);
    console.log(`  - Database: ${bmwAfter.eua_balance} EUA`);
    console.log(`  - Minting Logs: ${bmwMintLogsAfter.length}`);
    console.log(`Total Energies SA:`);
    console.log(`  - On-Chain: ${totalEuaAccount.amount} EUA`);
    console.log(`  - Database: ${totalAfter.eua_balance} EUA`);
    console.log(`  - Minting Logs: ${totalMintLogsAfter.length}`);
    console.log(`Full Amount of EUA Allocated:`);
    console.log(`  - On-Chain: ${Number(bmwEuaAccount.amount) + Number(totalEuaAccount.amount)} EUA`);
    console.log(`  - Database: ${globalTotalMinted} EUA`);
    console.log("✓ EUA tokens allocated successfully via API");
    console.log("✓ Database state matches on-chain state");
  });
});
