// src/networks/solana/constants.ts
import { PublicKey, StakeProgram } from '@solana/web3.js';

import liqsolCoreIDL from '../../assets/solana/idl/liqsol_core.json';
import liqSolTokenIDL from '../../assets/solana/idl/liqsol_token.json';
import validatorLeaderboardIDL from '../../assets/solana/idl/validator_leaderboard.json';
import transferHookIDL from '../../assets/solana/idl/transfer_hook.json';

/**
 * ---------------------------------------------------------------------------
 *  PROGRAM IDS
 * ---------------------------------------------------------------------------
 *
 * These should match:
 *  - `metadata.address` in your Anchor IDLs under src/assets/solana/idl/*.json
 *  - The program IDs printed by your deploy scripts
 */
export const PROGRAM_IDS = {
    LIQSOL_CORE: new PublicKey(liqsolCoreIDL.address),
    LIQSOL_TOKEN: new PublicKey(liqSolTokenIDL.address),
    VALIDATOR_LEADERBOARD: new PublicKey(validatorLeaderboardIDL.address),
    TRANSFER_HOOK: new PublicKey(transferHookIDL.address),
} as const;

export const {
    LIQSOL_CORE,
    LIQSOL_TOKEN,
    VALIDATOR_LEADERBOARD,
    TRANSFER_HOOK,
} = PROGRAM_IDS;

/**
 * ---------------------------------------------------------------------------
 *  PDA SEEDS (must match on-chain programs)
 * ---------------------------------------------------------------------------
 */

export const PDA_SEEDS = {
    // GLOBAL CONFIG
    GLOBAL_CONFIG: 'global_config',

    // liqsol_core: deposit / stake controller
    DEPOSIT_AUTHORITY: 'deposit_authority',
    VAULT: 'vault',
    RESERVE_POOL: 'reserve_pool',
    STAKE_CONTROLLER_STATE: 'stake_controller',
    PAYOUT_STATE: 'payout_state',

    // liqsol_token: mint + bucket
    LIQSOL_MINT: 'liqsol_mint',
    LIQSOL_MINT_AUTHORITY: 'mint_authority',
    BUCKET_AUTHORITY: 'liqsol_bucket',

    // distribution program (embedded in liqsol_core)
    DISTRIBUTION_STATE: 'distribution_state',
    // NOTE: in the current IDL, userRecord is keyed by the USER (wallet), not ATA
    USER_RECORD: 'user_record',

    // pay-rate / price history
    PAY_RATE_HISTORY: 'pay_rate_history',
    PRICE_HISTORY: 'price_history',

    // validator leaderboard (state + records)
    LEADERBOARD_STATE: 'leaderboard_state',
    VALIDATOR_RECORD: 'validator',
    GLOBAL_STAKE_INFO: 'global_stake_info',

    // Outpost / Wire pretokens
    OUTPOST_GLOBAL_STATE: 'outpost_global_state',
    LIQSOL_POOL: 'liqsol_pool',
    OUTPOST_ACCOUNT: 'outpost_account',
    USER_PRETOKEN_RECORD: 'user_pretoken_record',
    TRANCHE_STATE: 'tranche_state',

    // BAR module (bonded actor roles)
    BAR_STATE_SEED: 'bar_state',
    BONDED_ACTOR_SEED: 'bonded_actor',
    BOND_LEVEL_SEED: 'bond_level',

    // SPL transfer-hook extra account metas
    EXTRA_ACCOUNT_METAS: 'extra-account-metas',

    // Withdraw module / receipts
    WITHDRAW_GLOBAL: 'global',
    STAKE_ALLOCATION_STATE: 'stake_allocation_state',
    STAKE_METRICS: 'stake_metrics',
    MAINTENANCE_LEDGER: 'maintenance_ledger',
    WITHDRAW_MINT_AUTHORITY: 'mint_authority',
    MINT_METADATA: 'mint_metadata',
    LIQ_RECEIPT_DATA: 'liq_receipt_data',
    WITHDRAW_MINT: 'mint',
} as const;

// Global Config PDA
export const deriveGlobalConfigPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.GLOBAL_CONFIG)],
        LIQSOL_CORE,
    )[0];


/**
 * ---------------------------------------------------------------------------
 *  CORE / DISTRIBUTION / DEPOSIT PDAS
 * ---------------------------------------------------------------------------
 */

export const deriveLiqsolMintPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.LIQSOL_MINT)],
        LIQSOL_TOKEN,
    )[0];

export const deriveLiqsolMintAuthorityPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.LIQSOL_MINT_AUTHORITY)],
        LIQSOL_TOKEN,
    )[0];

export const deriveDepositAuthorityPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.DEPOSIT_AUTHORITY)],
        LIQSOL_CORE,
    )[0];

export const deriveVaultPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.VAULT)],
        LIQSOL_CORE,
    )[0];

export const deriveReservePoolPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.RESERVE_POOL)],
        LIQSOL_CORE,
    )[0];

export const deriveStakeControllerStatePda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.STAKE_CONTROLLER_STATE)],
        LIQSOL_CORE,
    )[0];

export const derivePayoutStatePda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.PAYOUT_STATE)],
        LIQSOL_CORE,
    )[0];

export const deriveBucketAuthorityPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.BUCKET_AUTHORITY)],
        LIQSOL_CORE,
    )[0];

export const deriveDistributionStatePda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.DISTRIBUTION_STATE)],
        LIQSOL_CORE,
    )[0];

/**
 * user_record PDA
 *
 * In the current liqsol_core IDL, this is keyed by USER (wallet),
 * not by token account.
 */
export const deriveUserRecordPda = (user: PublicKey) =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.USER_RECORD), user.toBuffer()],
        LIQSOL_CORE,
    )[0];

export const derivePayRateHistoryPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.PAY_RATE_HISTORY)],
        LIQSOL_CORE,
    )[0];

/**
 * ---------------------------------------------------------------------------
 *  WITHDRAW MODULE / RECEIPTS
 * ---------------------------------------------------------------------------
 */

export const deriveWithdrawGlobalPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.WITHDRAW_GLOBAL)],
        LIQSOL_CORE,
    )[0];

export const deriveStakeAllocationStatePda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.STAKE_ALLOCATION_STATE)],
        LIQSOL_CORE,
    )[0];

export const deriveStakeMetricsPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.STAKE_METRICS)],
        LIQSOL_CORE,
    )[0];

export const deriveMaintenanceLedgerPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.MAINTENANCE_LEDGER)],
        LIQSOL_CORE,
    )[0];

export const deriveWithdrawMintAuthorityPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.WITHDRAW_MINT_AUTHORITY)],
        LIQSOL_CORE,
    )[0];

export const deriveWithdrawMintMetadataPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.MINT_METADATA)],
        LIQSOL_CORE,
    )[0];

/**
 * NFT mint for withdrawal receipt, derived from nextReceiptId.
 */
export const deriveWithdrawNftMintPda = (receiptId: bigint | number) => {
    const receiptIdBytes = Buffer.alloc(8);
    // receiptId comes in as BN or bigint upstream → normalize
    const asBigInt =
        typeof receiptId === 'bigint'
            ? receiptId
            : BigInt(receiptId.toString());
    receiptIdBytes.writeBigUInt64LE(asBigInt);
    return PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.WITHDRAW_MINT), receiptIdBytes],
        LIQSOL_CORE,
    )[0];
};

export const deriveLiqReceiptDataPda = (nftMint: PublicKey) =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.LIQ_RECEIPT_DATA), nftMint.toBuffer()],
        LIQSOL_CORE,
    )[0];

/**
 * ---------------------------------------------------------------------------
 *  VALIDATOR LEADERBOARD
 * ---------------------------------------------------------------------------
 */

export const deriveLeaderboardStatePda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.LEADERBOARD_STATE)],
        VALIDATOR_LEADERBOARD,
    )[0];

export const deriveValidatorRecordPda = (voteAccount: PublicKey) =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.VALIDATOR_RECORD), voteAccount.toBuffer()],
        VALIDATOR_LEADERBOARD,
    )[0];

export const deriveStakeControllerVaultPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.VAULT)],
        LIQSOL_CORE,
    )[0];

/**
 * ---------------------------------------------------------------------------
 *  OUTPOST / PRETOKENS
 * ---------------------------------------------------------------------------
 */

export const deriveOutpostGlobalStatePda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.OUTPOST_GLOBAL_STATE)],
        LIQSOL_CORE,
    )[0];

export const deriveOutpostPoolAuthorityPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.LIQSOL_POOL)],
        LIQSOL_CORE,
    )[0];

export const deriveOutpostAccountPda = (user: PublicKey) =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.OUTPOST_ACCOUNT), user.toBuffer()],
        LIQSOL_CORE,
    )[0];

export const deriveUserPretokenRecordPda = (user: PublicKey) =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.USER_PRETOKEN_RECORD), user.toBuffer()],
        LIQSOL_CORE,
    )[0];

export const deriveTrancheStatePda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.TRANCHE_STATE)],
        LIQSOL_CORE,
    )[0];

export const derivePriceHistoryPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.PRICE_HISTORY)],
        LIQSOL_CORE,
    )[0];

/**
 * BAR (bonded actor roles)
 */

export const deriveBarConfigPda = () =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.BAR_STATE_SEED)],
        LIQSOL_CORE,
    )[0];

export const deriveBondLevelPda = (bondLevelId: number[]) =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.BOND_LEVEL_SEED), Buffer.from(bondLevelId)],
        LIQSOL_CORE,
    )[0];

export const deriveBondedActorPda = (actor: PublicKey) =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.BONDED_ACTOR_SEED), actor.toBuffer()],
        LIQSOL_CORE,
    )[0];

/**
 * SPL Transfer-hook: extra-account-metas PDA for the liqSOL mint.
 */
export const deriveExtraAccountMetaListPda = (liqsolMint: PublicKey) =>
    PublicKey.findProgramAddressSync(
        [Buffer.from(PDA_SEEDS.EXTRA_ACCOUNT_METAS), liqsolMint.toBuffer()],
        PROGRAM_IDS.TRANSFER_HOOK,
    )[0];

/**
 * Ephemeral stake account address used per-deposit.
 * On-chain convention: seed = `ephemeral_<u32>` under StakeProgram.programId.
 */
export const deriveEphemeralStakeAddress = async (
    user: PublicKey,
    seed: number,
): Promise<PublicKey> => {
    const seedStr = `ephemeral_${seed}`;
    return PublicKey.createWithSeed(user, seedStr, StakeProgram.programId);
};

/**
 * Constant keys (Chainlink)
 */
export const CHAINLINK_FEED = new PublicKey(
    'CH31Xns5z3M1cTAbKW34jcxPPciazARpijcHj9rxtemt',
);
export const CHAINLINK_PROGRAM = new PublicKey(
    'HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny',
);

/**
 * ---------------------------------------------------------------------------
 *  ECONOMICS & MATH CONSTANTS
 * ---------------------------------------------------------------------------
 */

// Same scale factor used on-chain for pay-rate math
export const PAY_RATE_SCALE_FACTOR = BigInt(1_000_000_000_000); // 10^12

// Default pay rate fallback used in tests & utils
export const DEFAULT_AVERAGE_PAY_RATE = BigInt(191_780_821);

// How many history entries to average when computing expected fee
export const DEFAULT_PAY_RATE_LOOKBACK = 5;

// Rent exemption for ephemeral stake account (lamports)
export const EPHEMERAL_RENT_EXEMPTION = 2_282_880;

// For convenience: lamports <-> SOL helpers (no RPC dependency)
export const LAMPORTS_PER_SOL = 1_000_000_000;

export const INDEX_SCALE = BigInt(1_000_000_000_000); // 1e12

export const lamportsToSol = (lamports: number | bigint): number =>
    Number(lamports) / LAMPORTS_PER_SOL;

export const solToLamports = (sol: number): bigint =>
    BigInt(Math.round(sol * LAMPORTS_PER_SOL));