//! Off-chain instruction builders. Fixed account order mirrors on-chain `process_*` comments.

use solana_program::pubkey::Pubkey;
use spl_associated_token_account::{
    get_associated_token_address, get_associated_token_address_with_program_id,
};
use steel::*;

use crate::{
    consts::{EXECUTOR_ADDRESS, FEE_COLLECTOR, USDC_MAINNET_MINT},
    instruction::{AdminPayout, AdminRouteFees, BuyTicket, Initialize, PlaceBet},
    state::{Treasury, User},
};

#[inline(always)]
pub fn program_id() -> Pubkey {
    crate::ID
}

// ── ATA helpers ──────────────────────────────────────────────────────────────

pub fn treasury_usdc_ata() -> Pubkey {
    get_associated_token_address(&Treasury::pda().0, &USDC_MAINNET_MINT)
}

pub fn treasury_usdc_ata_with_token_program(token_program: &Pubkey) -> Pubkey {
    get_associated_token_address_with_program_id(
        &Treasury::pda().0,
        &USDC_MAINNET_MINT,
        token_program,
    )
}

pub fn associated_usdc_ata(owner: Pubkey, token_program: &Pubkey) -> Pubkey {
    get_associated_token_address_with_program_id(&owner, &USDC_MAINNET_MINT, token_program)
}

pub fn usdc_ata(owner: Pubkey) -> Pubkey {
    get_associated_token_address(&owner, &USDC_MAINNET_MINT)
}

pub fn fee_collector_usdc_ata_with_token_program(token_program: &Pubkey) -> Pubkey {
    get_associated_token_address_with_program_id(&FEE_COLLECTOR, &USDC_MAINNET_MINT, token_program)
}

// ── Instruction builders ─────────────────────────────────────────────────────

/// One-time treasury initialization.
pub fn initialize(payer: Pubkey, token_program: Pubkey) -> Instruction {
    let treasury_address = Treasury::pda().0;
    let treasury_ata = treasury_usdc_ata_with_token_program(&token_program);
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(payer, true),
            AccountMeta::new(treasury_address, false),
            AccountMeta::new_readonly(USDC_MAINNET_MINT, false),
            AccountMeta::new(treasury_ata, false),
            AccountMeta::new_readonly(system_program::ID, false),
            AccountMeta::new_readonly(token_program, false),
            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
        ],
        data: Initialize {}.to_bytes(),
    }
}

/// Purchase gaming credits (tickets) with USDC.
///
/// Splits `amount` on-chain: 95% → treasury_ata (daily/weekly/buyback), 5% → fee_collector_ata
/// directly (user signs both transfers; team share never touches the treasury).
/// Credits the user's `User` PDA `balance` by the full `amount`.
///
/// **Pre-requisite:** the `FEE_COLLECTOR` USDC ATA must exist before calling this.
pub fn buy_ticket(user: Pubkey, amount: u64, token_program: Pubkey) -> Instruction {
    let user_ata = associated_usdc_ata(user, &token_program);
    let treasury_address = Treasury::pda().0;
    let treasury_ata = treasury_usdc_ata_with_token_program(&token_program);
    let user_pda = User::pda(&user).0;
    let fee_collector_ata = fee_collector_usdc_ata_with_token_program(&token_program);
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(user, true),
            AccountMeta::new(user_ata, false),
            AccountMeta::new(treasury_address, false),
            AccountMeta::new(treasury_ata, false),
            AccountMeta::new(user_pda, false),
            AccountMeta::new(fee_collector_ata, false),
            AccountMeta::new_readonly(USDC_MAINNET_MINT, false),
            AccountMeta::new_readonly(token_program, false),
            AccountMeta::new_readonly(system_program::ID, false),
        ],
        data: BuyTicket {
            amount: amount.to_le_bytes(),
        }
        .to_bytes(),
    }
}

/// Spend 1 ticket (TICKET_PRICE_MICROS) on a prediction for the given period.
/// One bet per round — the on-chain `User.last_bet_period` guard prevents double-betting.
/// User signs; `User.balance` is debited on-chain.
/// The indexer reads the `BetPlaced` event to record the bet and update market pools.
pub fn place_bet(user: Pubkey, side: u8, series_id: u16, period: u64) -> Instruction {
    let user_pda = User::pda(&user).0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(user, true),
            AccountMeta::new(user_pda, false),
        ],
        data: PlaceBet {
            side,
            _pad: [0; 1],
            series_id: series_id.to_le_bytes(),
            _pad2: [0; 4],
            period: period.to_le_bytes(),
        }
        .to_bytes(),
    }
}

/// Route `amount` µUSDC of claimed DBC / DAMM v2 fees into the treasury.
pub fn route_fees(executor: Pubkey, amount: u64, token_program: Pubkey) -> Instruction {
    let treasury_address = Treasury::pda().0;
    let treasury_ata = treasury_usdc_ata_with_token_program(&token_program);
    let executor_ata = associated_usdc_ata(executor, &token_program);
    let fee_collector_ata = fee_collector_usdc_ata_with_token_program(&token_program);
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(executor, true),
            AccountMeta::new(executor_ata, false),
            AccountMeta::new(treasury_address, false),
            AccountMeta::new(treasury_ata, false),
            AccountMeta::new(fee_collector_ata, false),
            AccountMeta::new_readonly(USDC_MAINNET_MINT, false),
            AccountMeta::new_readonly(token_program, false),
        ],
        data: AdminRouteFees {
            amount: amount.to_le_bytes(),
        }
        .to_bytes(),
    }
}

/// Convenience: `route_fees` signed by the canonical `EXECUTOR_ADDRESS`.
pub fn route_fees_default_executor(amount: u64, token_program: Pubkey) -> Instruction {
    route_fees(EXECUTOR_ADDRESS, amount, token_program)
}

/// Pay `amount` µUSDC from the treasury to `recipient_ata`.
pub fn payout(
    executor: Pubkey,
    recipient_ata: Pubkey,
    amount: u64,
    series_id: u16,
    period: u64,
    token_program: Pubkey,
) -> Instruction {
    let treasury_address = Treasury::pda().0;
    let treasury_ata = treasury_usdc_ata_with_token_program(&token_program);
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(executor, true),
            AccountMeta::new(treasury_address, false),
            AccountMeta::new(treasury_ata, false),
            AccountMeta::new(recipient_ata, false),
            AccountMeta::new_readonly(USDC_MAINNET_MINT, false),
            AccountMeta::new_readonly(token_program, false),
        ],
        data: AdminPayout {
            amount: amount.to_le_bytes(),
            series_id: series_id.to_le_bytes(),
            _pad_ix: [0; 6],
            period: period.to_le_bytes(),
        }
        .to_bytes(),
    }
}
