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

describe("Register Companies (API)", () => {
  it("Register Companies with USDC Vaults via Backend API", async () => {
    const ctx = getTestContext();
    const {
      connection,
      program,
      axumBaseUrl,
      registryPda,
      localAdminGermanyPda,
      localAdminFrancePda,
      company1Pda,
      company2Pda,
      localAdminGermany,
      localAdminFrance,
      company1Admin,
      company2Admin,
      euaMint,
      ghgMint,
      usdcMint,
      db,
    } = ctx;

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

    const companyCountBefore = await db.getCompanyCount();
    expect(companyCountBefore).to.equal(0);
    console.log("✓ No companies in database before registration");

    // Check local admin companies_managed count
    const germanyAdminBefore = await db.getLocalAdmin(localAdminGermanyPda.toBase58());
    const franceAdminBefore = await db.getLocalAdmin(localAdminFrancePda.toBase58());
    expect(germanyAdminBefore.companies_managed).to.equal(0);
    expect(franceAdminBefore.companies_managed).to.equal(0);

    // ============================================================
    // STEP 2: Calculate Token Vault Addresses for Company 1 (BMW)
    // ============================================================
    console.log("Calculating vault addresses for BMW...");

    const company1EuaVault = await getAssociatedTokenAddress(
      euaMint,
      company1Pda,
      true
    );

    const company1GhgVault = await getAssociatedTokenAddress(
      ghgMint,
      company1Pda,
      true
    );

    const company1UsdcVault = await getAssociatedTokenAddress(
      usdcMint,
      company1Pda,
      true
    );

    console.log(`✓ BMW EUA Vault: ${company1EuaVault.toBase58()}`);
    console.log(`✓ BMW GHG Vault: ${company1GhgVault.toBase58()}`);
    console.log(`✓ BMW USDC Vault: ${company1UsdcVault.toBase58()}`);

    // ============================================================
    // STEP 3: Register Company 1 (BMW) via API
    // ============================================================
    console.log("Registering BMW Manufacturing GmbH...");

    const bmwRequest = {
      registry: registryPda.toBase58(),
      local_admin: localAdminGermanyPda.toBase58(),
      company: company1Pda.toBase58(),
      eua_vault: company1EuaVault.toBase58(),
      ghg_vault: company1GhgVault.toBase58(),
      usdc_vault: company1UsdcVault.toBase58(),
      eua_mint: euaMint.toBase58(),
      ghg_mint: ghgMint.toBase58(),
      usdc_mint: usdcMint.toBase58(),
      company_admin: company1Admin.publicKey.toBase58(),
      payer: localAdminGermany.publicKey.toBase58(),
      company_name: "BMW Manufacturing GmbH",
    };

    const createBmwTxResponse = await fetch(
      `${axumBaseUrl}/api/registry/register-company`,
      {
        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 transaction: ${error}`);
    }

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

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

    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 transaction: ${error}`);
    }

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

    // ============================================================
    // STEP 4: Calculate Token Vault Addresses for Company 2 (Total)
    // ============================================================
    console.log("Calculating vault addresses for Total Energies...");

    const company2EuaVault = await getAssociatedTokenAddress(
      euaMint,
      company2Pda,
      true
    );

    const company2GhgVault = await getAssociatedTokenAddress(
      ghgMint,
      company2Pda,
      true
    );

    const company2UsdcVault = await getAssociatedTokenAddress(
      usdcMint,
      company2Pda,
      true
    );

    console.log(`✓ Total EUA Vault: ${company2EuaVault.toBase58()}`);
    console.log(`✓ Total GHG Vault: ${company2GhgVault.toBase58()}`);
    console.log(`✓ Total USDC Vault: ${company2UsdcVault.toBase58()}`);

    // ============================================================
    // STEP 5: Register Company 2 (Total Energies) via API
    // ============================================================
    console.log("Registering Total Energies SA...");

    const totalRequest = {
      registry: registryPda.toBase58(),
      local_admin: localAdminFrancePda.toBase58(),
      company: company2Pda.toBase58(),
      eua_vault: company2EuaVault.toBase58(),
      ghg_vault: company2GhgVault.toBase58(),
      usdc_vault: company2UsdcVault.toBase58(),
      eua_mint: euaMint.toBase58(),
      ghg_mint: ghgMint.toBase58(),
      usdc_mint: usdcMint.toBase58(),
      company_admin: company2Admin.publicKey.toBase58(),
      payer: localAdminFrance.publicKey.toBase58(),
      company_name: "Total Energies SA",
    };

    const createTotalTxResponse = await fetch(
      `${axumBaseUrl}/api/registry/register-company`,
      {
        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 transaction: ${error}`);
    }

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

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

    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 transaction: ${error}`);
    }

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

    // ============================================================
    // STEP 6: Store Vault Addresses in Test Context
    // ============================================================
    setTestContext({
      company1EuaVault,
      company1GhgVault,
      company1UsdcVault,
      company2EuaVault,
      company2GhgVault,
      company2UsdcVault,
    });

    console.log("✓ Vault addresses stored in test context");

    // ============================================================
    // STEP 7: Verify On-Chain State - Registry
    // ============================================================
    console.log("Verifying registry state...");

    const registry = await program.account.registry.fetch(registryPda);
    expect(registry.companyCount).to.equal(2);
    console.log(`✓ Registry company count: ${registry.companyCount}`);

    // ============================================================
    // STEP 8: Verify On-Chain State - Company 1 (BMW)
    // ============================================================
    console.log("Verifying BMW company state...");

    const company1 = await program.account.company.fetch(company1Pda);
    expect(company1.name).to.equal("BMW Manufacturing GmbH");
    expect(company1.companyAdmin.equals(company1Admin.publicKey)).to.be.true;
    expect(company1.localAdmin.equals(localAdminGermanyPda)).to.be.true;
    expect(company1.isActive).to.be.true;
    expect(company1.euaVault.equals(company1EuaVault)).to.be.true;
    expect(company1.ghgVault.equals(company1GhgVault)).to.be.true;
    expect(company1.usdcVault.equals(company1UsdcVault)).to.be.true;

    console.log(`✓ BMW company verified: ${company1.name}`);

    // ============================================================
    // STEP 9: Verify On-Chain State - Company 2 (Total)
    // ============================================================
    console.log("Verifying Total Energies company state...");

    const company2 = await program.account.company.fetch(company2Pda);
    expect(company2.name).to.equal("Total Energies SA");
    expect(company2.companyAdmin.equals(company2Admin.publicKey)).to.be.true;
    expect(company2.localAdmin.equals(localAdminFrancePda)).to.be.true;
    expect(company2.isActive).to.be.true;
    expect(company2.euaVault.equals(company2EuaVault)).to.be.true;
    expect(company2.ghgVault.equals(company2GhgVault)).to.be.true;
    expect(company2.usdcVault.equals(company2UsdcVault)).to.be.true;

    console.log(`✓ Total company verified: ${company2.name}`);

    // ============================================================
    // STEP 10: Verify Local Admin State Updates
    // ============================================================
    console.log("Verifying local admin state updates...");

    const germanyLocalAdmin = await program.account.localAdmin.fetch(
      localAdminGermanyPda
    );
    expect(germanyLocalAdmin.companiesManaged).to.equal(1);
    console.log(
      `✓ Germany local admin companies managed: ${germanyLocalAdmin.companiesManaged}`
    );

    const franceLocalAdmin = await program.account.localAdmin.fetch(
      localAdminFrancePda
    );
    expect(franceLocalAdmin.companiesManaged).to.equal(1);
    console.log(
      `✓ France local admin companies managed: ${franceLocalAdmin.companiesManaged}`
    );

    // ============================================================
    // STEP 11: Verify Token Vaults Created
    // ============================================================
    console.log("Verifying token vault accounts created...");

    const bmwEuaVaultInfo = await connection.getAccountInfo(company1EuaVault);
    const bmwGhgVaultInfo = await connection.getAccountInfo(company1GhgVault);
    const bmwUsdcVaultInfo = await connection.getAccountInfo(company1UsdcVault);

    expect(bmwEuaVaultInfo).to.not.be.null;
    expect(bmwGhgVaultInfo).to.not.be.null;
    expect(bmwUsdcVaultInfo).to.not.be.null;
    console.log("✓ BMW token vaults created");

    const totalEuaVaultInfo = await connection.getAccountInfo(company2EuaVault);
    const totalGhgVaultInfo = await connection.getAccountInfo(company2GhgVault);
    const totalUsdcVaultInfo = await connection.getAccountInfo(company2UsdcVault);

    expect(totalEuaVaultInfo).to.not.be.null;
    expect(totalGhgVaultInfo).to.not.be.null;
    expect(totalUsdcVaultInfo).to.not.be.null;
    console.log("✓ Total token vaults created");

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

    // Wait for companies to appear in DB
    const bmwInDb = await db.waitForCompany(company1Pda.toBase58(), 5000);
    expect(bmwInDb).to.be.true;

    const totalInDb = await db.waitForCompany(company2Pda.toBase58(), 5000);
    expect(totalInDb).to.be.true;

    // Get company count
    const companyCountAfter = await db.getCompanyCount();
    expect(companyCountAfter).to.equal(2);
    console.log(`✓ Database has ${companyCountAfter} companies`);

    // Verify BMW in DB
    const bmwDbRecord = await db.getCompany(company1Pda.toBase58());
    expect(bmwDbRecord).to.not.be.undefined;
    expect(bmwDbRecord.company_name).to.equal("BMW Manufacturing GmbH");
    expect(bmwDbRecord.company_admin_pubkey).to.equal(company1Admin.publicKey.toBase58());
    expect(bmwDbRecord.local_admin_pubkey).to.equal(localAdminGermanyPda.toBase58());
    expect(bmwDbRecord.is_active).to.be.true;
    expect(bmwDbRecord.eua_vault).to.equal(company1EuaVault.toBase58());
    expect(bmwDbRecord.ghg_vault).to.equal(company1GhgVault.toBase58());
    expect(bmwDbRecord.usdc_vault).to.equal(company1UsdcVault.toBase58());
    expect(bmwDbRecord.locked_eua_amount).to.equal("0");
    expect(bmwDbRecord.locked_usdc_amount).to.equal("0");
    expect(bmwDbRecord.eua_balance).to.equal("0");
    expect(bmwDbRecord.ghg_balance).to.equal("0");
    expect(bmwDbRecord.usdc_balance).to.equal("0");
    console.log(`✓ BMW verified in database: ${bmwDbRecord.company_name}`);

    // Verify Total in DB
    const totalDbRecord = await db.getCompany(company2Pda.toBase58());
    expect(totalDbRecord).to.not.be.undefined;
    expect(totalDbRecord.company_name).to.equal("Total Energies SA");
    expect(totalDbRecord.company_admin_pubkey).to.equal(company2Admin.publicKey.toBase58());
    expect(totalDbRecord.local_admin_pubkey).to.equal(localAdminFrancePda.toBase58());
    expect(totalDbRecord.is_active).to.be.true;
    expect(totalDbRecord.eua_vault).to.equal(company2EuaVault.toBase58());
    expect(totalDbRecord.ghg_vault).to.equal(company2GhgVault.toBase58());
    expect(totalDbRecord.usdc_vault).to.equal(company2UsdcVault.toBase58());
    expect(totalDbRecord.locked_eua_amount).to.equal("0");
    expect(totalDbRecord.locked_usdc_amount).to.equal("0");
    expect(totalDbRecord.eua_balance).to.equal("0");
    expect(totalDbRecord.ghg_balance).to.equal("0");
    expect(totalDbRecord.usdc_balance).to.equal("0");
    console.log(`✓ Total verified in database: ${totalDbRecord.company_name}`);

    // Verify local admin companies_managed updated in DB
    const germanyAdminAfter = await db.getLocalAdmin(localAdminGermanyPda.toBase58());
    const franceAdminAfter = await db.getLocalAdmin(localAdminFrancePda.toBase58());

    // Note: companies_managed might not be updated yet depending on event handler implementation
    // If your event handler updates this, uncomment:
    // expect(germanyAdminAfter.companies_managed).to.equal(1);
    // expect(franceAdminAfter.companies_managed).to.equal(1);

    // Verify companies by local admin
    const germanyCompanies = await db.getCompaniesByLocalAdmin(localAdminGermanyPda.toBase58());
    expect(germanyCompanies.length).to.equal(1);
    expect(germanyCompanies[0].company_name).to.equal("BMW Manufacturing GmbH");

    const franceCompanies = await db.getCompaniesByLocalAdmin(localAdminFrancePda.toBase58());
    expect(franceCompanies.length).to.equal(1);
    expect(franceCompanies[0].company_name).to.equal("Total Energies SA");

    console.log("\n=== Summary ===");
    console.log(`On-Chain: ${registry.companyCount} companies`);
    console.log(`Database: ${companyCountAfter} companies`);
    console.log("✓ Companies registered with USDC vaults successfully via API");
    console.log("✓ Database state matches on-chain state");
  });
});
