//! Off-chain instruction builders (Ore **`api::sdk`** style): fixed account order mirrors on-chain **`process_*`** comment headers.
//!
//! For **`ClaimPositionFee`**, pass the **`cp\_amm`** account tail (13 metas, Anchor `ClaimPositionFeeCtx` order).

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, EXECUTOR_PAY_DAILY_JACKPOT, EXECUTOR_PAY_WEEKLY_JACKPOT, FEE_COLLECTOR,
        USDC_MAINNET_MINT,
    },
    instruction::*,
    state::{Ledger, Market, Position, Treasury},
};

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

#[inline(always)]
fn executor_treasury_ix_bytes(kind: u8, pool: u8, amount: u64) -> Vec<u8> {
    ExecutorTreasury {
        kind,
        pool,
        _pad: [0; 6],
        amount: amount.to_le_bytes(),
    }
    .to_bytes()
}

/// SPL USDC ATA (**classic** SPL Token program). For Token-2022 mints use [`treasury_usdc_ata_with_token_program`] / [`associated_usdc_ata`] with the mint owner's program id (for example **`spl_token_2022`**'s **`id()`**).
#[inline(always)]
pub fn treasury_usdc_ata() -> Pubkey {
    get_associated_token_address(&Treasury::pda().0, &USDC_MAINNET_MINT)
}

#[inline(always)]
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)
}

#[inline(always)]
pub fn associated_usdc_ata(owner: Pubkey, token_program: &Pubkey) -> Pubkey {
    get_associated_token_address_with_program_id(&owner, &USDC_MAINNET_MINT, token_program)
}

/// SPL USDC ATA for **`owner`** (classic Token program — mainnet Circle USDC).
#[inline(always)]
pub fn usdc_ata(owner: Pubkey) -> Pubkey {
    get_associated_token_address(&owner, &USDC_MAINNET_MINT)
}

/// Team-fee SPL destination used by **`BuyTickets`** / **`ClaimPositionFee`** (classic Token mint).
#[inline(always)]
pub fn fee_collector_usdc_ata() -> Pubkey {
    get_associated_token_address(&FEE_COLLECTOR, &USDC_MAINNET_MINT)
}

#[inline(always)]
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)
}

/// **`previous_market`** / **`previous_position`** PDAs (`period - 1`). For **`period == 0`** the program never checks these accounts; placeholders avoid accidental signer flags.
#[inline(always)]
pub fn buy_tickets_or_place_bet_previous_accounts(user: Pubkey, period: u64) -> (Pubkey, Pubkey) {
    match period.checked_sub(1) {
        Some(p) => (Market::pda(p).0, Position::pda(p, user).0),
        None => (system_program::ID, system_program::ID),
    }
}

// let [payer_info, treasury_info, mint_info, treasury_ata, system_program, token_program, ata_program]

/// One-time **`Initialize`**. **`payer`** must be [`ADMIN_ADDRESS`](crate::consts::ADMIN_ADDRESS).
///
/// **`token_program`** must be the mint owner's program id (`spl_token` or `spl_token_2022` for the configured USDC mint).
pub fn initialize(payer: Pubkey, token_program: Pubkey) -> Instruction {
    let treasury_address = Treasury::pda().0;
    let mint_address = USDC_MAINNET_MINT;
    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(mint_address, 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(),
    }
}

// let [user, treasury_info, ledger_info, mint_info, user_ata, treasury_ata, team_ata, system_program, token_program, ata_program, previous_market_info, previous_position_info]

/// Builds **`BuyTickets`**.
pub fn buy_tickets(user: Pubkey, period: u64, amount: u64, token_program: Pubkey) -> Instruction {
    let treasury_address = Treasury::pda().0;
    let ledger_address = Ledger::pda(user).0;
    let mint_address = USDC_MAINNET_MINT;
    let user_ata = associated_usdc_ata(user, &token_program);
    let treasury_ata_addr = treasury_usdc_ata_with_token_program(&token_program);
    let team_ata_addr = fee_collector_usdc_ata_with_token_program(&token_program);
    let (prev_mkt, prev_pos) = buy_tickets_or_place_bet_previous_accounts(user, period);
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(user, true),
            AccountMeta::new(treasury_address, false),
            AccountMeta::new(ledger_address, false),
            AccountMeta::new_readonly(mint_address, false),
            AccountMeta::new(user_ata, false),
            AccountMeta::new(treasury_ata_addr, false),
            AccountMeta::new(team_ata_addr, false),
            AccountMeta::new_readonly(system_program::ID, false),
            AccountMeta::new_readonly(token_program, false),
            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
            AccountMeta::new_readonly(prev_mkt, false),
            AccountMeta::new_readonly(prev_pos, false),
        ],
        data: BuyTickets {
            period: period.to_le_bytes(),
            amount: amount.to_le_bytes(),
        }
        .to_bytes(),
    }
}

// let [user, ledger_info, market_info, position_info, previous_market_info, previous_position_info, system_program]

/// Builds **`PlaceBet`**.
pub fn place_bet(user: Pubkey, period: u64, ticket_units: u64, side: u8) -> Instruction {
    let ledger_address = Ledger::pda(user).0;
    let treasury_address = Treasury::pda().0;
    let market_address = Market::pda(period).0;
    let position_address = Position::pda(period, user).0;
    let (prev_mkt, prev_pos) = buy_tickets_or_place_bet_previous_accounts(user, period);
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(user, true),
            AccountMeta::new(ledger_address, false),
            AccountMeta::new_readonly(treasury_address, false),
            AccountMeta::new(market_address, false),
            AccountMeta::new(position_address, false),
            AccountMeta::new_readonly(prev_mkt, false),
            AccountMeta::new_readonly(prev_pos, false),
            AccountMeta::new_readonly(system_program::ID, false),
        ],
        data: PlaceBet {
            period: period.to_le_bytes(),
            ticket_units: ticket_units.to_le_bytes(),
            side,
            _pad: [0; 7],
        }
        .to_bytes(),
    }
}

/// Builds **`AdminInstantSettlement`**. **`authority`** must be [`crate::consts::EXECUTOR_ADDRESS`].
/// Subsidy is computed on-chain (see [`crate::instruction::AdminInstantSettlement`]).
pub fn settle_market(authority: Pubkey, period: u64, pyth_price_feed: Pubkey) -> Instruction {
    let treasury_address = Treasury::pda().0;
    let market_address = Market::pda(period).0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(authority, true),
            AccountMeta::new(treasury_address, false),
            AccountMeta::new(market_address, false),
            AccountMeta::new_readonly(pyth_price_feed, false),
        ],
        data: AdminInstantSettlement {
            period: period.to_le_bytes(),
        }
        .to_bytes(),
    }
}

/// Builds **`InitMarket`**. **`authority`** must be [`crate::consts::EXECUTOR_ADDRESS`] for **create** and **open-anchor crank**.
pub fn init_market(
    authority: Pubkey,
    period: u64,
    open_ts: i64,
    close_ts: i64,
    pyth_price_feed: Pubkey,
    rules_hash: [u8; 32],
) -> Instruction {
    let market_address = Market::pda(period).0;
    let treasury_address = Treasury::pda().0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(authority, true),
            AccountMeta::new(market_address, false),
            AccountMeta::new_readonly(treasury_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
            AccountMeta::new_readonly(pyth_price_feed, false),
        ],
        data: InitMarket {
            period: period.to_le_bytes(),
            open_ts: open_ts.to_le_bytes(),
            close_ts: close_ts.to_le_bytes(),
            pyth_price_feed: pyth_price_feed.to_bytes(),
            rules_hash,
        }
        .to_bytes(),
    }
}

// let [executor, treasury_info, treasury_ata, recipient_ata, mint_info, token_program, market_info, position_info, ledger_info]

/// **`recipient_ata`** must be **[`associated_usdc_ata`](associated_usdc_ata)(winning_authority, &token_program)**.
pub fn distribute_market_reward(
    executor: Pubkey,
    winning_authority: Pubkey,
    market_period: u64,
    token_program: Pubkey,
) -> Instruction {
    let treasury_address = Treasury::pda().0;
    let treasury_ata_addr = treasury_usdc_ata_with_token_program(&token_program);
    let recipient_ata = associated_usdc_ata(winning_authority, &token_program);
    let mint_address = USDC_MAINNET_MINT;
    let market_address = Market::pda(market_period).0;
    let position_address = Position::pda(market_period, winning_authority).0;
    let ledger_address = Ledger::pda(winning_authority).0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(executor, true),
            AccountMeta::new(treasury_address, false),
            AccountMeta::new(treasury_ata_addr, false),
            AccountMeta::new(recipient_ata, false),
            AccountMeta::new_readonly(mint_address, false),
            AccountMeta::new_readonly(token_program, false),
            AccountMeta::new_readonly(market_address, false),
            AccountMeta::new(position_address, false),
            AccountMeta::new(ledger_address, false),
        ],
        data: executor_treasury_ix_bytes(EXECUTOR_KIND_DISTRIBUTE_MARKET_REWARD, 0, 0),
    }
}

/// Convenience: fixed executor pubkey ([`EXECUTOR_ADDRESS`](crate::consts::EXECUTOR_ADDRESS)).
pub fn distribute_market_reward_default_executor(
    winning_authority: Pubkey,
    market_period: u64,
    token_program: Pubkey,
) -> Instruction {
    distribute_market_reward(EXECUTOR_ADDRESS, winning_authority, market_period, token_program)
}

/// **`ExecutorTreasury`** **`EXECUTOR_KIND_MARKET_LINE_RELEASE`**: call after **`DISTRIBUTE_MARKET_REWARD`** when **`winning_total > 0`** so **`Treasury::next_market_period`** advances (**executor-trusted ordering** vs payouts).
///
/// **Accounts:** **`executor`**, **`treasury`**, **`market`** (readonly).
pub fn release_market_line(executor: Pubkey, period: u64) -> Instruction {
    let treasury_address = Treasury::pda().0;
    let market_address = Market::pda(period).0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(executor, true),
            AccountMeta::new(treasury_address, false),
            AccountMeta::new_readonly(market_address, false),
        ],
        data: executor_treasury_ix_bytes(EXECUTOR_KIND_MARKET_LINE_RELEASE, 0, period),
    }
}

#[inline(always)]
pub fn release_market_line_default_executor(period: u64) -> Instruction {
    release_market_line(EXECUTOR_ADDRESS, period)
}

// let [executor, treasury_info, treasury_ata, mint_info, token_program, recipient_ata]

/// **`pool`**: [`EXECUTOR_PAY_DAILY_JACKPOT`](crate::consts::EXECUTOR_PAY_DAILY_JACKPOT) or [`EXECUTOR_PAY_WEEKLY_JACKPOT`](crate::consts::EXECUTOR_PAY_WEEKLY_JACKPOT).
///
/// **`recipient_ata`** is any SPL token account holding USDC (**mint + program-id** enforced on-chain); use **[`associated_usdc_ata`]** when building the ATA address.
pub fn executor_treasury_pay_recipient_account(
    executor: Pubkey,
    recipient_ata: Pubkey,
    amount: u64,
    pool: u8,
    token_program: Pubkey,
) -> Instruction {
    let treasury_address = Treasury::pda().0;
    let treasury_ata_addr = treasury_usdc_ata_with_token_program(&token_program);
    let mint_address = USDC_MAINNET_MINT;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(executor, true),
            AccountMeta::new(treasury_address, false),
            AccountMeta::new(treasury_ata_addr, false),
            AccountMeta::new_readonly(mint_address, false),
            AccountMeta::new_readonly(token_program, false),
            AccountMeta::new(recipient_ata, false),
        ],
        data: executor_treasury_ix_bytes(EXECUTOR_KIND_LANE_PAY, pool, amount),
    }
}

/// Same as **`executor_treasury_pay_recipient_account`** with **`recipient_ata`** = **[`associated_usdc_ata`](associated_usdc_ata)(recipient_owner, token_program)**.
#[inline(always)]
pub fn executor_treasury_pay(
    executor: Pubkey,
    recipient_owner: Pubkey,
    amount: u64,
    pool: u8,
    token_program: Pubkey,
) -> Instruction {
    executor_treasury_pay_recipient_account(
        executor,
        associated_usdc_ata(recipient_owner, &token_program),
        amount,
        pool,
        token_program,
    )
}

/// Daily jackpot lane preset.
#[inline(always)]
pub fn executor_treasury_pay_daily(
    executor: Pubkey,
    recipient_owner: Pubkey,
    amount: u64,
    token_program: Pubkey,
) -> Instruction {
    executor_treasury_pay_recipient_account(
        executor,
        associated_usdc_ata(recipient_owner, &token_program),
        amount,
        EXECUTOR_PAY_DAILY_JACKPOT,
        token_program,
    )
}

/// Weekly jackpot lane preset.
#[inline(always)]
pub fn executor_treasury_pay_weekly(
    executor: Pubkey,
    recipient_owner: Pubkey,
    amount: u64,
    token_program: Pubkey,
) -> Instruction {
    executor_treasury_pay_recipient_account(
        executor,
        associated_usdc_ata(recipient_owner, &token_program),
        amount,
        EXECUTOR_PAY_WEEKLY_JACKPOT,
        token_program,
    )
}

// Streak indices 0..8: executor, owner, treasury, treasury_ata, fee_collector_ata, mint, system_program, token_program, owner_usdc_ata
// Indices 9..22: cp_amm ClaimPositionFeeCtx (see `programs/program/src/admin/claim_position_fee.rs`)

/// CPI prefix metas only; caller appends **`cp\_amm`** accounts.
pub fn claim_position_fee_accounts_prefix(
    owner: Pubkey,
    owner_usdc_ata: Pubkey,
    token_program: Pubkey,
) -> [AccountMeta; 9] {
    let executor = EXECUTOR_ADDRESS;
    let treasury_address = Treasury::pda().0;
    let treasury_ata_addr = treasury_usdc_ata_with_token_program(&token_program);
    let team_ata_addr = fee_collector_usdc_ata_with_token_program(&token_program);
    [
        AccountMeta::new(executor, true),
        AccountMeta::new(owner, true),
        AccountMeta::new(treasury_address, false),
        AccountMeta::new(treasury_ata_addr, false),
        AccountMeta::new(team_ata_addr, false),
        AccountMeta::new_readonly(USDC_MAINNET_MINT, false),
        AccountMeta::new_readonly(system_program::ID, false),
        AccountMeta::new_readonly(token_program, false),
        AccountMeta::new(owner_usdc_ata, false),
    ]
}

pub fn claim_position_fee(
    owner: Pubkey,
    owner_usdc_ata: Pubkey,
    cp_amm_accounts: &[AccountMeta],
    token_program: Pubkey,
) -> Instruction {
    let prefix = claim_position_fee_accounts_prefix(owner, owner_usdc_ata, token_program);
    let mut accounts = Vec::with_capacity(9 + cp_amm_accounts.len());
    accounts.extend_from_slice(&prefix);
    accounts.extend_from_slice(cp_amm_accounts);
    Instruction {
        program_id: crate::ID,
        accounts,
        data: ClaimPositionFee {}.to_bytes(),
    }
}
