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

describe("Withdraw USDC (API)", () => {
  it("Total Withdraws USDC from Vault", async () => {
    const ctx = getTestContext();
    const {
      connection,
      axumBaseUrl,
      company2Pda,
      company2UsdcVault,
      company2AdminUsdcAta,
      company2Admin,
      db,
    } = ctx;

    const withdrawAmount = 50_000_000; // 50 USDC

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

    const companyBefore = await db.getCompany(company2Pda.toBase58());
    const usdcLogsBefore = await db.getUsdcLogs(company2Pda.toBase58());
    const totalWithdrawnBefore = await db.getTotalUsdcWithdrawn(company2Pda.toBase58());
    const vaultBefore = await getAccount(connection, company2UsdcVault);
    const ataBefore = await getAccount(connection, company2AdminUsdcAta);

    console.log(`✓ Company 2 USDC balance before: ${companyBefore.usdc_balance}`);
    console.log(`✓ Vault balance before: ${vaultBefore.amount}`);
    console.log(`✓ Admin ATA balance before: ${ataBefore.amount}`);
    console.log(`✓ USDC logs count before: ${usdcLogsBefore.length}`);
    console.log(`✓ Total withdrawn before: ${totalWithdrawnBefore}`);

    // ============================================================
    // 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: withdrawAmount,
    };

    // ============================================================
    // STEP 3: Call Axum API to get unsigned transaction
    // ============================================================
    console.log("Calling API to create withdraw USDC transaction...");

    const createTxResponse = await fetch(
      `${axumBaseUrl}/api/secondary-market/withdraw-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}`);
    }

    type WithdrawUsdcResponse = {
      transaction_base64: string;
      message: string;
    };

    const data = (await createTxResponse.json()) as WithdrawUsdcResponse;
    const { transaction_base64, message } = data;
    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 vaultAfter = await getAccount(connection, company2UsdcVault);
    const ataAfter = await getAccount(connection, company2AdminUsdcAta);

    const expectedVaultBalance = Number(vaultBefore.amount) - withdrawAmount;
    const expectedAtaBalance = Number(ataBefore.amount) + withdrawAmount;

    expect(Number(vaultAfter.amount)).to.equal(expectedVaultBalance);
    expect(Number(ataAfter.amount)).to.equal(expectedAtaBalance);

    const usdcDivisor = 1_000_000;
    console.log(`✓ Vault balance after: ${Number(vaultAfter.amount) / usdcDivisor} USDC`);
    console.log(`✓ Admin ATA balance after: ${Number(ataAfter.amount) / usdcDivisor} USDC`);
    console.log(`✓ Withdrawn: ${withdrawAmount / usdcDivisor} USDC`);

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

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

    // Calculate expected new balance in DB
    const expectedDbBalance = Number(companyBefore.usdc_balance) - withdrawAmount;

    // Wait for company balance to be updated
    const balanceUpdated = await db.waitForCompanyUsdcBalance(
      company2Pda.toBase58(),
      expectedDbBalance.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]; // Most recent log
    expect(latestLog.company_pubkey).to.equal(company2Pda.toBase58());
    expect(latestLog.operation_type).to.equal('Withdraw');
    expect(latestLog.amount).to.equal(withdrawAmount.toString());
    expect(latestLog.new_vault_balance).to.equal(expectedVaultBalance.toString());
    console.log(`✓ USDC withdrawal log verified (Log ID: ${latestLog.id})`);

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

    // Verify total withdrawn
    const totalWithdrawnAfter = await db.getTotalUsdcWithdrawn(company2Pda.toBase58());
    expect(totalWithdrawnAfter).to.equal(BigInt(withdrawAmount));
    console.log(`✓ Total USDC withdrawn: ${totalWithdrawnAfter}`);

    // Verify all logs for Company 2
    const depositLogs = await db.getUsdcLogsByType(company2Pda.toBase58(), 'Deposit');
    const withdrawLogs = await db.getUsdcLogsByType(company2Pda.toBase58(), 'Withdraw');

    console.log(`✓ Total deposit logs: ${depositLogs.length}`);
    console.log(`✓ Total withdraw logs: ${withdrawLogs.length}`);

    console.log("✓ Total withdrew 50 USDC");
    console.log("✓ Database state matches on-chain state");
  });
});
