use anchor_lang::{InstructionData, ToAccountMetas};
use solana_program::{instruction::Instruction, pubkey::Pubkey};

use crate::constants::SYSTEM_PROGRAM_ID;

pub struct InitUserParams {
    pub owner: Pubkey,
    /// Whether the owner is signing the transaction.
    ///
    /// The on-chain `init_user` handler treats the `owner` account as an
    /// `UncheckedAccount` and inspects `owner.is_signer` at runtime:
    /// - `true`  → caller-supplied spend limits are applied.
    /// - `false` → defaults to zero limits with a 1-day timeframe
    ///   (permissionless vault creation).
    ///
    /// Anchor's auto-derived account metas mark `owner` as a non-signer, so
    /// callers that want the spend-limit branch must set this to `true` to
    /// add `owner` to the message's required-signer set.
    pub owner_is_signer: bool,
    pub payer: Pubkey,
    pub spend_limit_per_transaction: u64,
    pub spend_limit_per_timeframe: u64,
    pub next_timeframe_reset_timestamp: u64,
    pub timeframe_in_seconds: u64,
}

pub fn init_user(params: &InitUserParams) -> Instruction {
    let vault = pyra_accounts::get_vault(&params.owner);

    let mut accounts = crate::pyra_program::client::accounts::InitUser {
        vault,
        owner: params.owner,
        payer: params.payer,
        system_program: SYSTEM_PROGRAM_ID,
    }
    .to_account_metas(None);

    if params.owner_is_signer {
        // `InitUser` always emits an `owner` meta — Anchor would have to break
        // the account list to remove it. Crash loudly if that invariant ever
        // changes so callers don't silently ship a non-signing transaction.
        #[allow(clippy::expect_used)]
        let meta = accounts
            .iter_mut()
            .find(|m| m.pubkey == params.owner)
            .expect("InitUser metas must include owner");
        meta.is_signer = true;
    }

    let data = crate::pyra_program::client::args::InitUser {
        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,
    }
    .data();

    Instruction {
        program_id: pyra_accounts::PYRA_PROGRAM_ID,
        accounts,
        data,
    }
}

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

    fn base_params(owner_is_signer: bool) -> InitUserParams {
        InitUserParams {
            owner: Pubkey::new_unique(),
            owner_is_signer,
            payer: 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 owner_is_signer_false_keeps_owner_as_non_signer() {
        let params = base_params(false);
        let ix = init_user(&params);
        let owner_meta = ix
            .accounts
            .iter()
            .find(|m| m.pubkey == params.owner)
            .unwrap();
        assert!(!owner_meta.is_signer);
        // Payer is always a signer.
        let payer_meta = ix
            .accounts
            .iter()
            .find(|m| m.pubkey == params.payer)
            .unwrap();
        assert!(payer_meta.is_signer);
    }

    #[test]
    fn owner_is_signer_true_flips_owner_meta() {
        let params = base_params(true);
        let ix = init_user(&params);
        let owner_meta = ix
            .accounts
            .iter()
            .find(|m| m.pubkey == params.owner)
            .unwrap();
        assert!(owner_meta.is_signer);
    }
}
