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

describe("Deposit USDC (API)", () => {
  it("Setup: Mint USDC to Company Admins", async () => {
    const ctx = getTestContext();
    const {
      connection,
      globalAdmin,
      usdcMint,
      company1Admin,
      company2Admin,
    } = ctx;

    console.log("Creating USDC ATAs for company admins...");

    const company1AdminUsdcAta = await createAssociatedTokenAccount(
      connection,
      globalAdmin,
      usdcMint,
      company1Admin.publicKey
    );

    const company2AdminUsdcAta = await createAssociatedTokenAccount(
      connection,
      globalAdmin,
      usdcMint,
      company2Admin.publicKey
    );

    console.log(`Company 1 Admin USDC ATA: ${company1AdminUsdcAta.toBase58()}`);
    console.log(`Company 2 Admin USDC ATA: ${company2AdminUsdcAta.toBase58()}`);

    // Mint USDC to company admins (simulating they bought USDC)
    await mintTo(
      connection,
      globalAdmin,
      usdcMint,
      company1AdminUsdcAta,
      globalAdmin,
      2_000_000_000 // 2000 USDC (with 6 decimals)
    );

    await mintTo(
      connection,
      globalAdmin,
      usdcMint,
      company2AdminUsdcAta,
      globalAdmin,
      2_000_000_000 // 2000 USDC
    );

    // Verify balances
    const company1Balance = await getAccount(connection, company1AdminUsdcAta);
    const company2Balance = await getAccount(connection, company2AdminUsdcAta);

    expect(company1Balance.amount.toString()).to.equal("2000000000");
    expect(company2Balance.amount.toString()).to.equal("2000000000");

    // Store in context
    setTestContext({
      company1AdminUsdcAta,
      company2AdminUsdcAta,
    });

    console.log("✓ USDC minted to company admins");
    console.log(`  Company 1 Admin: 2000 USDC`);
    console.log(`  Company 2 Admin: 2000 USDC`);
  });

  it("Company 1 Deposits 500 USDC to Vault via API", async () => {
    const ctx = getTestContext();
    const {
      connection,
      program,
      axumBaseUrl,
      company1Pda,
      company1UsdcVault,
      company1AdminUsdcAta,
      company1Admin,
      db,
    } = ctx;

    const depositAmount = 500_000_000; // 500 USDC (6 decimals)

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

    const companyBefore = await db.getCompany(company1Pda.toBase58());
    const usdcLogsBefore = await db.getUsdcLogs(company1Pda.toBase58());
    const totalDepositedBefore = await db.getTotalUsdcDeposited(company1Pda.toBase58());

    console.log(`✓ Company 1 USDC balance before: ${companyBefore.usdc_balance}`);
    console.log(`✓ USDC logs count before: ${usdcLogsBefore.length}`);
    console.log(`✓ Total deposited before: ${totalDepositedBefore}`);

    // ============================================================
    // STEP 2: Create API request payload
    // ============================================================
    const request = {
      company: company1Pda.toBase58(),
      company_admin_usdc_ata: company1AdminUsdcAta.toBase58(),
      company_usdc_vault: company1UsdcVault.toBase58(),
      company_admin: company1Admin.publicKey.toBase58(),
      token_program: TOKEN_PROGRAM_ID.toBase58(),
      amount: depositAmount,
    };

    console.log("Calling API to create deposit USDC transaction for Company 1...");

    // ============================================================
    // STEP 3: Call Axum API to get unsigned transaction
    // ============================================================
    const createTxResponse = await fetch(
      `${axumBaseUrl}/api/secondary-market/deposit-usdc`,
      {
        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}`);

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

    console.log("Transaction signed locally by Company 1 Admin");

    // ============================================================
    // STEP 5: 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 6: Verify On-Chain State
    // ============================================================
    console.log("Verifying on-chain state...");

    const vaultAccount = await getAccount(connection, company1UsdcVault);
    expect(vaultAccount.amount.toString()).to.equal("500000000");

    const usdcDivisor = 1_000_000;
    console.log("✓ Company 1 deposited USDC");
    console.log(`  Vault balance: ${Number(vaultAccount.amount) / usdcDivisor} USDC`);

    const adminAtaAccount = await getAccount(connection, company1AdminUsdcAta);
    expect(adminAtaAccount.amount.toString()).to.equal("1500000000"); // 2000 - 500 = 1500

    console.log(`  Admin ATA balance: ${Number(adminAtaAccount.amount) / usdcDivisor} USDC`);

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

    // Wait for USDC log to be created
    const logCreated = await db.waitForUsdcLog(
      company1Pda.toBase58(),
      'Deposit',
      BigInt(depositAmount),
      5000
    );
    expect(logCreated).to.be.true;

    // Wait for company balance to be updated
    const balanceUpdated = await db.waitForCompanyUsdcBalance(
      company1Pda.toBase58(),
      depositAmount.toString(),
      5000
    );
    expect(balanceUpdated).to.be.true;

    // Verify USDC log details
    const usdcLogsAfter = await db.getUsdcLogs(company1Pda.toBase58());
    expect(usdcLogsAfter.length).to.be.greaterThan(usdcLogsBefore.length);

    const latestLog = usdcLogsAfter[0];
    expect(latestLog.company_pubkey).to.equal(company1Pda.toBase58());
    expect(latestLog.operation_type).to.equal('Deposit');
    expect(latestLog.amount).to.equal(depositAmount.toString());
    expect(latestLog.new_vault_balance).to.equal(depositAmount.toString());
    console.log(`✓ USDC deposit log verified (Log ID: ${latestLog.id})`);

    // Verify company balance in DB
    const companyAfter = await db.getCompany(company1Pda.toBase58());
    expect(companyAfter.usdc_balance).to.equal(depositAmount.toString());
    console.log(`✓ Company 1 USDC balance in DB: ${companyAfter.usdc_balance}`);

    // Verify total deposited
    const totalDepositedAfter = await db.getTotalUsdcDeposited(company1Pda.toBase58());
    expect(totalDepositedAfter).to.equal(BigInt(depositAmount));
    console.log(`✓ Total USDC deposited: ${totalDepositedAfter}`);

    console.log("✓ Database state matches on-chain state");
  });

  it("Company 2 Deposits 800 USDC to Vault via API", async () => {
    const ctx = getTestContext();
    const {
      connection,
      program,
      axumBaseUrl,
      company2Pda,
      company2UsdcVault,
      company2AdminUsdcAta,
      company2Admin,
      db,
    } = ctx;

    const depositAmount = 800_000_000; // 800 USDC (6 decimals)

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

    const companyBefore = await db.getCompany(company2Pda.toBase58());
    const usdcLogsBefore = await db.getUsdcLogs(company2Pda.toBase58());
    const totalDepositedBefore = await db.getTotalUsdcDeposited(company2Pda.toBase58());

    console.log(`✓ Company 2 USDC balance before: ${companyBefore.usdc_balance}`);
    console.log(`✓ USDC logs count before: ${usdcLogsBefore.length}`);
    console.log(`✓ Total deposited before: ${totalDepositedBefore}`);

    // ============================================================
    // STEP 2: Create API request payload
    // ============================================================
    const request = {
      company: company2Pda.toBase58(),
      company_admin_usdc_ata: company2AdminUsdcAta.toBase58(),
      company_usdc_vault: company2UsdcVault.toBase58(),
      company_admin: company2Admin.publicKey.toBase58(),
      token_program: TOKEN_PROGRAM_ID.toBase58(),
      amount: depositAmount,
    };

    console.log("Calling API to create deposit USDC transaction for Company 2...");

    // ============================================================
    // STEP 3: Call Axum API to get unsigned transaction
    // ============================================================
    const createTxResponse = await fetch(
      `${axumBaseUrl}/api/secondary-market/deposit-usdc`,
      {
        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}`);

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

    console.log("Transaction signed locally by Company 2 Admin");

    // ============================================================
    // STEP 5: 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 6: Verify On-Chain State
    // ============================================================
    console.log("Verifying on-chain state...");

    const vaultAccount = await getAccount(connection, company2UsdcVault);
    expect(vaultAccount.amount.toString()).to.equal("800000000");

    const usdcDivisor = 1_000_000;
    console.log("✓ Company 2 deposited USDC");
    console.log(`  Vault balance: ${Number(vaultAccount.amount) / usdcDivisor} USDC`);

    const adminAtaAccount = await getAccount(connection, company2AdminUsdcAta);
    expect(adminAtaAccount.amount.toString()).to.equal("1200000000"); // 2000 - 800 = 1200

    console.log(`  Admin ATA balance: ${Number(adminAtaAccount.amount) / usdcDivisor} USDC`);

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

    // Wait for USDC log to be created
    const logCreated = await db.waitForUsdcLog(
      company2Pda.toBase58(),
      'Deposit',
      BigInt(depositAmount),
      5000
    );
    expect(logCreated).to.be.true;

    // Wait for company balance to be updated
    const balanceUpdated = await db.waitForCompanyUsdcBalance(
      company2Pda.toBase58(),
      depositAmount.toString(),
      5000
    );
    expect(balanceUpdated).to.be.true;

    // Verify USDC log details
    const usdcLogsAfter = await db.getUsdcLogs(company2Pda.toBase58());
    expect(usdcLogsAfter.length).to.be.greaterThan(usdcLogsBefore.length);

    const latestLog = usdcLogsAfter[0];
    expect(latestLog.company_pubkey).to.equal(company2Pda.toBase58());
    expect(latestLog.operation_type).to.equal('Deposit');
    expect(latestLog.amount).to.equal(depositAmount.toString());
    expect(latestLog.new_vault_balance).to.equal(depositAmount.toString());
    console.log(`✓ USDC deposit log verified (Log ID: ${latestLog.id})`);

    // Verify company balance in DB
    const companyAfter = await db.getCompany(company2Pda.toBase58());
    expect(companyAfter.usdc_balance).to.equal(depositAmount.toString());
    console.log(`✓ Company 2 USDC balance in DB: ${companyAfter.usdc_balance}`);

    // Verify total deposited
    const totalDepositedAfter = await db.getTotalUsdcDeposited(company2Pda.toBase58());
    expect(totalDepositedAfter).to.equal(BigInt(depositAmount));
    console.log(`✓ Total USDC deposited: ${totalDepositedAfter}`);

    console.log("✓ Database state matches on-chain state");
  });

  it("Verify Final Balances", async () => {
    const ctx = getTestContext();
    const {
      connection,
      company1Pda,
      company2Pda,
      company1UsdcVault,
      company2UsdcVault,
      company1AdminUsdcAta,
      company2AdminUsdcAta,
      db,
    } = ctx;

    // On-chain verification
    const company1Vault = await getAccount(connection, company1UsdcVault);
    const company2Vault = await getAccount(connection, company2UsdcVault);
    const company1AdminAta = await getAccount(connection, company1AdminUsdcAta);
    const company2AdminAta = await getAccount(connection, company2AdminUsdcAta);

    const usdcDivisor = 1_000_000;

    console.log("\n=== Final On-Chain USDC Balances ===");
    console.log(`Company 1 Vault: ${Number(company1Vault.amount) / usdcDivisor} USDC`);
    console.log(`Company 1 Admin ATA: ${Number(company1AdminAta.amount) / usdcDivisor} USDC`);
    console.log(`Company 2 Vault: ${Number(company2Vault.amount) / usdcDivisor} USDC`);
    console.log(`Company 2 Admin ATA: ${Number(company2AdminAta.amount) / usdcDivisor} USDC`);

    expect(company1Vault.amount.toString()).to.equal("500000000");
    expect(company2Vault.amount.toString()).to.equal("800000000");
    expect(company1AdminAta.amount.toString()).to.equal("1500000000");
    expect(company2AdminAta.amount.toString()).to.equal("1200000000");

    // Database verification
    const company1Db = await db.getCompany(company1Pda.toBase58());
    const company2Db = await db.getCompany(company2Pda.toBase58());

    console.log("\n=== Final Database USDC Balances ===");
    console.log(`Company 1 DB Balance: ${Number(company1Db.usdc_balance) / usdcDivisor} USDC`);
    console.log(`Company 2 DB Balance: ${Number(company2Db.usdc_balance) / usdcDivisor} USDC`);

    expect(company1Db.usdc_balance).to.equal("500000000");
    expect(company2Db.usdc_balance).to.equal("800000000");

    // Verify deposit logs
    const company1Logs = await db.getUsdcLogsByType(company1Pda.toBase58(), 'Deposit');
    const company2Logs = await db.getUsdcLogsByType(company2Pda.toBase58(), 'Deposit');

    console.log(`\nCompany 1 Deposit Logs: ${company1Logs.length}`);
    console.log(`Company 2 Deposit Logs: ${company2Logs.length}`);

    expect(company1Logs.length).to.equal(1);
    expect(company2Logs.length).to.equal(1);

    console.log("✓ All balances verified successfully");
    console.log("✓ Database state matches on-chain state");
  });
});
