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

describe("Register Local Admins (API)", () => {
  it("Register Local Admins via Backend API", async () => {
    const ctx = getTestContext();
    const {
      connection,
      program,
      axumBaseUrl,
      globalAdmin,
      registryPda,
      localAdminGermanyPda,
      localAdminFrancePda,
      localAdminGermany,
      localAdminFrance,
      euaMint,
      usdcMint,
      db,
    } = ctx;

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

    const localAdminCountBefore = await db.getLocalAdminCount();
    expect(localAdminCountBefore).to.equal(0);
    console.log("✓ No local admins in database before registration");

    // ============================================================
    // STEP 2: Calculate vault addresses for Germany Local Admin
    // ============================================================
    const localAdminGermanyEuaVault = await getAssociatedTokenAddress(
      euaMint,
      localAdminGermanyPda,
      true
    );
    const localAdminGermanyUsdcVault = await getAssociatedTokenAddress(
      usdcMint,
      localAdminGermanyPda,
      true
    );

    // ============================================================
    // STEP 3: Register Germany Local Admin
    // ============================================================
    console.log("Registering Germany local admin...");

    const germanyRequest = {
      registry: registryPda.toBase58(),
      local_admin: localAdminGermanyPda.toBase58(),
      admin_pubkey: localAdminGermany.publicKey.toBase58(),
      global_admin: globalAdmin.publicKey.toBase58(),
      state_name: "Germany",
      eua_vault: localAdminGermanyEuaVault.toBase58(),
      usdc_vault: localAdminGermanyUsdcVault.toBase58(),
      eua_mint: euaMint.toBase58(),
      usdc_mint: usdcMint.toBase58(),
    };

    const createGermanyTxResponse = await fetch(
      `${axumBaseUrl}/api/registry/register-local-admin`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(germanyRequest),
      }
    );

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

    const { transaction_base64: germanyTxBase64 } =
      await createGermanyTxResponse.json();

    const germanyTxBuffer = Buffer.from(germanyTxBase64, "base64");
    const germanyTransaction = Transaction.from(germanyTxBuffer);
    germanyTransaction.sign(globalAdmin);

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

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

    const { signature: germanySignature } = await submitGermanyTxResponse.json();
    console.log(`✓ Germany transaction signature: ${germanySignature}`);

    // ============================================================
    // STEP 4: Calculate vault addresses for France Local Admin
    // ============================================================
    const localAdminFranceEuaVault = await getAssociatedTokenAddress(
      euaMint,
      localAdminFrancePda,
      true
    );
    const localAdminFranceUsdcVault = await getAssociatedTokenAddress(
      usdcMint,
      localAdminFrancePda,
      true
    );

    // ============================================================
    // STEP 5: Register France Local Admin
    // ============================================================
    console.log("Registering France local admin...");

    const franceRequest = {
      registry: registryPda.toBase58(),
      local_admin: localAdminFrancePda.toBase58(),
      admin_pubkey: localAdminFrance.publicKey.toBase58(),
      global_admin: globalAdmin.publicKey.toBase58(),
      state_name: "France",
      eua_vault: localAdminFranceEuaVault.toBase58(),
      usdc_vault: localAdminFranceUsdcVault.toBase58(),
      eua_mint: euaMint.toBase58(),
      usdc_mint: usdcMint.toBase58(),
    };

    const createFranceTxResponse = await fetch(
      `${axumBaseUrl}/api/registry/register-local-admin`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(franceRequest),
      }
    );

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

    const { transaction_base64: franceTxBase64 } =
      await createFranceTxResponse.json();

    const franceTxBuffer = Buffer.from(franceTxBase64, "base64");
    const franceTransaction = Transaction.from(franceTxBuffer);
    franceTransaction.sign(globalAdmin);

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

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

    const { signature: franceSignature } = await submitFranceTxResponse.json();
    console.log(`✓ France transaction signature: ${franceSignature}`);

    // ============================================================
    // STEP 6: Allocate 3000 EUA to Germany Local Admin
    // ============================================================
    console.log("Allocating 3000 EUA tokens to Germany Local Admin...");

    const germanyEuaAmount = 3000;
    const germanyEuaRequest = {
      registry: registryPda.toBase58(),
      local_admin: localAdminGermanyPda.toBase58(),
      eua_mint: euaMint.toBase58(),
      eua_vault: localAdminGermanyEuaVault.toBase58(),
      global_admin: globalAdmin.publicKey.toBase58(),
      token_program: TOKEN_PROGRAM_ID.toBase58(),
      amount: germanyEuaAmount,
    };

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

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

    const { transaction_base64: germanyEuaTxBase64 } =
      await createGermanyEuaTxResponse.json();

    const germanyEuaTxBuffer = Buffer.from(germanyEuaTxBase64, "base64");
    const germanyEuaTransaction = Transaction.from(germanyEuaTxBuffer);
    germanyEuaTransaction.sign(globalAdmin);

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

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

    const { signature: germanyEuaSignature } = await submitGermanyEuaTxResponse.json();
    console.log(`✓ Germany EUA mint transaction signature: ${germanyEuaSignature}`);

    // ============================================================
    // STEP 7: Allocate 3000 EUA to France Local Admin
    // ============================================================
    console.log("Allocating 3000 EUA tokens to France Local Admin...");

    const franceEuaAmount = 3000;
    const franceEuaRequest = {
      registry: registryPda.toBase58(),
      local_admin: localAdminFrancePda.toBase58(),
      eua_mint: euaMint.toBase58(),
      eua_vault: localAdminFranceEuaVault.toBase58(),
      global_admin: globalAdmin.publicKey.toBase58(),
      token_program: TOKEN_PROGRAM_ID.toBase58(),
      amount: franceEuaAmount,
    };

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

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

    const { transaction_base64: franceEuaTxBase64 } =
      await createFranceEuaTxResponse.json();

    const franceEuaTxBuffer = Buffer.from(franceEuaTxBase64, "base64");
    const franceEuaTransaction = Transaction.from(franceEuaTxBuffer);
    franceEuaTransaction.sign(globalAdmin);

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

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

    const { signature: franceEuaSignature } = await submitFranceEuaTxResponse.json();
    console.log(`✓ France EUA mint transaction signature: ${franceEuaSignature}`);

    // Store local admin vaults in context
    setTestContext({
      localAdminGermanyEuaVault,
      localAdminGermanyUsdcVault,
      localAdminFranceEuaVault,
      localAdminFranceUsdcVault,
    });

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

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

    // ============================================================
    // STEP 9: Verify On-Chain State - Germany Local Admin
    // ============================================================
    console.log("Verifying Germany local admin state...");

    const germanyLocalAdmin = await program.account.localAdmin.fetch(
      localAdminGermanyPda
    );
    expect(germanyLocalAdmin.stateName).to.equal("Germany");
    expect(germanyLocalAdmin.admin.equals(localAdminGermany.publicKey)).to.be.true;
    expect(germanyLocalAdmin.isActive).to.be.true;
    expect(germanyLocalAdmin.companiesManaged).to.equal(0);
    console.log(`✓ Germany local admin verified: ${germanyLocalAdmin.stateName}`);

    // ============================================================
    // STEP 10: Verify On-Chain State - France Local Admin
    // ============================================================
    console.log("Verifying France local admin state...");

    const franceLocalAdmin = await program.account.localAdmin.fetch(
      localAdminFrancePda
    );
    expect(franceLocalAdmin.stateName).to.equal("France");
    expect(franceLocalAdmin.admin.equals(localAdminFrance.publicKey)).to.be.true;
    expect(franceLocalAdmin.isActive).to.be.true;
    expect(franceLocalAdmin.companiesManaged).to.equal(0);
    console.log(`✓ France local admin verified: ${franceLocalAdmin.stateName}`);

    // ============================================================
    // STEP 11: Verify EUA Token Balances
    // ============================================================
    console.log("Verifying local admin EUA token balances...");

    const germanyEuaAccount = await getAccount(connection, localAdminGermanyEuaVault);
    expect(germanyEuaAccount.amount.toString()).to.equal(germanyEuaAmount.toString());
    console.log(`✓ Germany Local Admin EUA vault balance: ${germanyEuaAccount.amount} EUA`);

    const franceEuaAccount = await getAccount(connection, localAdminFranceEuaVault);
    expect(franceEuaAccount.amount.toString()).to.equal(franceEuaAmount.toString());
    console.log(`✓ France Local Admin EUA vault balance: ${franceEuaAccount.amount} EUA`);

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

    // Wait for Germany local admin to appear in DB
    const germanyInDb = await db.waitForLocalAdmin(localAdminGermanyPda.toBase58(), 5000);
    expect(germanyInDb).to.be.true;

    // Wait for France local admin to appear in DB
    const franceInDb = await db.waitForLocalAdmin(localAdminFrancePda.toBase58(), 5000);
    expect(franceInDb).to.be.true;

    // Get local admin count
    const localAdminCountAfter = await db.getLocalAdminCount();
    expect(localAdminCountAfter).to.equal(2);
    console.log(`✓ Database has ${localAdminCountAfter} local admins`);

    // Verify Germany local admin in DB
    const germanyDbRecord = await db.getLocalAdmin(localAdminGermanyPda.toBase58());
    expect(germanyDbRecord).to.not.be.undefined;
    expect(germanyDbRecord.state_name).to.equal("Germany");
    expect(germanyDbRecord.admin_pubkey).to.equal(localAdminGermany.publicKey.toBase58());
    expect(germanyDbRecord.is_active).to.be.true;
    expect(germanyDbRecord.companies_managed).to.equal(0);
    console.log(`✓ Germany local admin verified in database: ${germanyDbRecord.state_name}`);

    // Verify France local admin in DB
    const franceDbRecord = await db.getLocalAdmin(localAdminFrancePda.toBase58());
    expect(franceDbRecord).to.not.be.undefined;
    expect(franceDbRecord.state_name).to.equal("France");
    expect(franceDbRecord.admin_pubkey).to.equal(localAdminFrance.publicKey.toBase58());
    expect(franceDbRecord.is_active).to.be.true;
    expect(franceDbRecord.companies_managed).to.equal(0);
    console.log(`✓ France local admin verified in database: ${franceDbRecord.state_name}`);

    console.log("\n=== Summary ===");
    console.log(`On-Chain: ${registry.localAdminCount} local admins`);
    console.log(`Database: ${localAdminCountAfter} local admins`);
    console.log("✓ Local admins registered and funded successfully via API");
    console.log("✓ Database state matches on-chain state");
  });
});
