use solana_program::instruction::Instruction;
use solana_pubkey::Pubkey;

use crate::{
    fulfill_update_spend_limits, initiate_update_spend_limits, FulfillUpdateSpendLimitsParams,
    InitiateUpdateSpendLimitsParams,
};

pub struct UpdateSpendLimitsWithAdminParams {
    pub owner: Pubkey,
    /// Spend limits order account. Should be a freshly generated keypair. Must
    /// sign the transaction.
    pub spend_limits_order: Pubkey,
    pub payer: Pubkey,
    /// Must sign the transaction in order to skip the time lock.
    pub admin: Pubkey,
    pub spend_limit_per_transaction: u64,
    pub spend_limit_per_timeframe: u64,
    pub next_timeframe_reset_timestamp: u64,
    pub timeframe_in_seconds: u64,
}

/// Initiate then immediately fulfil a spend-limits update in a single
/// transaction. Admin must sign so the on-chain handler skips the time-lock
/// delay.
///
/// For the time-lock-respecting flow (initiate now, fulfil later without
/// admin), call [`initiate_update_spend_limits`] and
/// [`fulfill_update_spend_limits`] directly in separate transactions.
pub fn update_spend_limits_with_admin(
    params: &UpdateSpendLimitsWithAdminParams,
) -> Vec<Instruction> {
    let mut instructions = Vec::new();

    let initiate_params = InitiateUpdateSpendLimitsParams {
        owner: params.owner,
        spend_limits_order: params.spend_limits_order,
        payer: params.payer,
        spend_limit_per_transaction: params.spend_limit_per_transaction,
        spend_limit_per_timeframe: params.spend_limit_per_timeframe,
        next_timeframe_reset_timestamp: params.next_timeframe_reset_timestamp,
        timeframe_in_seconds: params.timeframe_in_seconds,
    };
    let initiate_ix = initiate_update_spend_limits(&initiate_params);
    instructions.push(initiate_ix);

    let fulfill_params = FulfillUpdateSpendLimitsParams {
        spend_limits_order: params.spend_limits_order,
        owner: params.owner,
        order_payer: params.payer,
        admin: params.admin,
        // Admin signs in the same tx to skip the time lock.
        admin_is_signer: true,
    };
    let fulfill_ix = fulfill_update_spend_limits(&fulfill_params);
    instructions.push(fulfill_ix);

    instructions
}

#[cfg(test)]
#[allow(
    clippy::allow_attributes,
    clippy::allow_attributes_without_reason,
    clippy::unwrap_used
)]
mod tests {
    use super::*;

    fn base_params() -> UpdateSpendLimitsWithAdminParams {
        UpdateSpendLimitsWithAdminParams {
            owner: Pubkey::new_unique(),
            spend_limits_order: Pubkey::new_unique(),
            payer: Pubkey::new_unique(),
            admin: Pubkey::new_unique(),
            spend_limit_per_transaction: 100,
            spend_limit_per_timeframe: 1_000,
            next_timeframe_reset_timestamp: 0,
            timeframe_in_seconds: 86_400,
        }
    }

    #[test]
    fn emits_initiate_then_fulfill_in_order() {
        let params = base_params();
        let ixs = update_spend_limits_with_admin(&params);
        assert_eq!(ixs.len(), 2);
        // Both target the Pyra program
        assert_eq!(ixs[0].program_id, pyra_accounts::PYRA_PROGRAM_ID);
        assert_eq!(ixs[1].program_id, pyra_accounts::PYRA_PROGRAM_ID);
        // Initiate vs fulfill — full-vector compare, no offset arithmetic
        // to drift if the discriminator scheme changes.
        assert_ne!(ixs[0].data, ixs[1].data);
    }

    #[test]
    fn fulfill_carries_admin_signer() {
        let params = base_params();
        let ixs = update_spend_limits_with_admin(&params);
        let fulfill = &ixs[1];
        let admin_meta = fulfill
            .accounts
            .iter()
            .find(|m| m.pubkey == params.admin)
            .unwrap();
        assert!(
            admin_meta.is_signer,
            "admin must sign on the fulfill ix to skip the time lock"
        );
    }
}
