use solana_program::program::invoke_signed;

use crate::access_control::structs::EphemeralMembersArgs;
use crate::compat::{self, Compat, Modern};

pub const CREATE_EPHEMERAL_PERMISSION_DISCRIMINATOR: u64 = 6;

#[derive(Debug)]
pub struct CreateEphemeralPermission<'a> {
    pub permissioned_account: compat::Pubkey,
    pub permission: compat::Pubkey,
    pub payer: compat::Pubkey,
    pub vault: compat::Pubkey,
    pub magic_program: compat::Pubkey,
    pub permission_program: compat::Pubkey,
    pub args: &'a EphemeralMembersArgs,
}

impl<'a> CreateEphemeralPermission<'a> {
    pub fn instruction(&self) -> Result<compat::Instruction, compat::ProgramError> {
        let accounts = vec![
            compat::AccountMeta::new(self.payer, true),
            compat::AccountMeta::new_readonly(self.permissioned_account, true),
            compat::AccountMeta::new(self.permission, false),
            compat::AccountMeta::new(self.vault, false),
            compat::AccountMeta::new_readonly(self.magic_program, false),
        ];
        let mut bytes = vec![0; EphemeralMembersArgs::required_bytes(self.args.members.len())];
        self.args
            .to_bytes(&mut bytes)
            .map_err(|_| compat::ProgramError::InvalidInstructionData)?;
        let data = [
            CREATE_EPHEMERAL_PERMISSION_DISCRIMINATOR
                .to_le_bytes()
                .to_vec(),
            bytes,
        ]
        .concat();

        Ok(compat::Instruction {
            program_id: self.permission_program,
            accounts,
            data,
        })
    }
}

pub struct CreateEphemeralPermissionCpi<'a> {
    pub permissioned_account: compat::AccountInfo<'a>,
    pub permission: compat::AccountInfo<'a>,
    pub payer: compat::AccountInfo<'a>,
    pub vault: compat::AccountInfo<'a>,
    pub magic_program: compat::AccountInfo<'a>,
    pub permission_program: compat::AccountInfo<'a>,
    pub args: EphemeralMembersArgs,
}

impl<'a> CreateEphemeralPermissionCpi<'a> {
    pub fn invoke(self) -> compat::ProgramResult {
        self.invoke_signed(&[])
    }

    pub fn invoke_signed(self, signers: &[&[&[u8]]]) -> compat::ProgramResult {
        let ix = CreateEphemeralPermission {
            permissioned_account: *self.permissioned_account.key,
            permission: *self.permission.key,
            payer: *self.payer.key,
            vault: *self.vault.key,
            magic_program: *self.magic_program.key,
            permission_program: *self.permission_program.key,
            args: &self.args,
        }
        .instruction()?
        .modern();

        invoke_signed(
            &ix,
            &[
                self.payer.modern(),
                self.permissioned_account.modern(),
                self.permission.modern(),
                self.vault.modern(),
                self.magic_program.modern(),
            ],
            signers,
        )
        .compat()
    }
}
