use std::mem::size_of;
use spl_account_compression::{error::AccountCompressionError, ConcurrentMerkleTree};
use crate::state::{AddressContainer, IndexedReference, Info, PublicInfo, User};
use {
    crate::{
        errors::ErrorCode,
        state::{Action, ActionType, DelegateAuthority, ServiceDelegation},
    },
    anchor_lang::prelude::*,
};
pub fn assert_authority<'info>(
    action: Action,
    index: Option<u8>,
    signer_key: Pubkey,
    authority: Option<Pubkey>,
    delegate_authority: &Option<Account<'info, DelegateAuthority>>,
) -> Result<()> {
    match action.action_type {
        ActionType::Public => Ok(()),
        ActionType::Restricted {
            delegations: action_delegations,
        } => {
            if let Some(authority) = authority {
                if authority == signer_key {
                    return Ok(());
                }
                Err(ErrorCode::Unauthorized.into())
            } else if let Some(delegate_authority) = delegate_authority {
                for action_delegation in action_delegations {
                    for authority_delegation in &delegate_authority.delegations {
                        match action_delegation {
                            ServiceDelegation::HiveControl {
                                permission: action_permission,
                            } => {
                                if let ServiceDelegation::HiveControl {
                                    permission: authority_permission,
                                } = authority_delegation
                                {
                                    if action_permission == authority_permission {
                                        return Ok(());
                                    }
                                }
                                continue;
                            }
                            ServiceDelegation::AssetAssembler {
                                index: _,
                                permission: action_permission,
                            } => {
                                if let ServiceDelegation::AssetAssembler {
                                    index: authority_index,
                                    permission: authority_permission,
                                } = authority_delegation
                                {
                                    if index.is_some()
                                        && index.unwrap() == *authority_index
                                        && action_permission == authority_permission
                                    {
                                        return Ok(());
                                    }
                                }
                                continue;
                            }
                            ServiceDelegation::AssetManager {
                                index: _,
                                permission: action_permission,
                            } => {
                                if let ServiceDelegation::AssetManager {
                                    index: authority_index,
                                    permission: authority_permission,
                                } = authority_delegation
                                {
                                    if index.is_some()
                                        && index.unwrap() == *authority_index
                                        && action_permission == authority_permission
                                    {
                                        return Ok(());
                                    }
                                }
                                continue;
                            }
                            ServiceDelegation::CurrencyManager {
                                permission: action_permission,
                            } => {
                                if let ServiceDelegation::CurrencyManager {
                                    permission: authority_permission,
                                } = authority_delegation
                                {
                                    if action_permission == authority_permission {
                                        return Ok(());
                                    }
                                }
                                continue;
                            }
                            ServiceDelegation::NectarStaking {
                                index: action_index,
                                permission: action_permission,
                            } => {
                                if let ServiceDelegation::NectarStaking {
                                    index: authority_index,
                                    permission: authority_permission,
                                } = authority_delegation
                                {
                                    if action_index == authority_index
                                        && action_permission == authority_permission
                                    {
                                        return Ok(());
                                    }
                                }
                                continue;
                            }
                            ServiceDelegation::NectarMissions {
                                index: action_index,
                                permission: action_permission,
                            } => {
                                if let ServiceDelegation::NectarMissions {
                                    index: authority_index,
                                    permission: authority_permission,
                                } = authority_delegation
                                {
                                    if action_index == authority_index
                                        && action_permission == authority_permission
                                    {
                                        return Ok(());
                                    }
                                }
                                continue;
                            }
                            ServiceDelegation::BuzzGuild {
                                index: action_index,
                                permission: action_permission,
                            } => {
                                if let ServiceDelegation::BuzzGuild {
                                    index: authority_index,
                                    permission: authority_permission,
                                } = authority_delegation
                                {
                                    if action_index == authority_index
                                        && action_permission == authority_permission
                                    {
                                        return Ok(());
                                    }
                                }
                                continue;
                            }
                        }
                    }
                }
                Err(ErrorCode::Unauthorized.into())
            } else {
                Err(ErrorCode::Unauthorized.into())
            }
        }
    }
}
pub fn assert_indexed_reference<'info>(
    indexed_reference: &IndexedReference,
    address_container: &Account<'info, AddressContainer>,
    address: Pubkey,
) -> Result<bool> {
    let account_info = &address_container.to_account_info();
    let mint_at_ref = address_container.addresses[indexed_reference.index_in_container as usize];
    let address_key = Pubkey::create_program_address(
        &[
            b"address_container",
            format!("{:?}", address_container.role).as_bytes(),
            address_container.associated_with.as_ref(),
            &[indexed_reference.address_container_index],
            &[address_container.bump],
        ],
        &crate::ID,
    )
    .unwrap();
    Ok(mint_at_ref == address && address_key == account_info.key())
}
pub fn assert_user<'info>(user: &Account<'info, User>, signer: Pubkey) -> Result<()> {
    msg!("Checking User");
    if user.primary_wallet == signer {
        return Ok(());
    }
    if user
        .secondary_wallets
        .iter()
        .find(|secondary_wallet| **secondary_wallet == signer)
        .is_some()
    {
        return Ok(());
    }
    Err(ErrorCode::Unauthorized.into())
}
pub fn merkle_tree_get_size(max_depth: u32, max_buffer_size: u32) -> Result<usize> {
    let data = match (max_depth, max_buffer_size) {
        (3, 8) => Ok(size_of::<ConcurrentMerkleTree<3, 8>>()),
        (5, 8) => Ok(size_of::<ConcurrentMerkleTree<5, 8>>()),
        (14, 64) => Ok(size_of::<ConcurrentMerkleTree<14, 64>>()),
        (14, 256) => Ok(size_of::<ConcurrentMerkleTree<14, 256>>()),
        (14, 1024) => Ok(size_of::<ConcurrentMerkleTree<14, 1024>>()),
        (14, 2048) => Ok(size_of::<ConcurrentMerkleTree<14, 2048>>()),
        (15, 64) => Ok(size_of::<ConcurrentMerkleTree<15, 64>>()),
        (16, 64) => Ok(size_of::<ConcurrentMerkleTree<16, 64>>()),
        (17, 64) => Ok(size_of::<ConcurrentMerkleTree<17, 64>>()),
        (18, 64) => Ok(size_of::<ConcurrentMerkleTree<18, 64>>()),
        (19, 64) => Ok(size_of::<ConcurrentMerkleTree<19, 64>>()),
        (20, 64) => Ok(size_of::<ConcurrentMerkleTree<20, 64>>()),
        (20, 256) => Ok(size_of::<ConcurrentMerkleTree<20, 256>>()),
        (20, 1024) => Ok(size_of::<ConcurrentMerkleTree<20, 1024>>()),
        (20, 2048) => Ok(size_of::<ConcurrentMerkleTree<20, 2048>>()),
        (24, 64) => Ok(size_of::<ConcurrentMerkleTree<24, 64>>()),
        (24, 256) => Ok(size_of::<ConcurrentMerkleTree<24, 256>>()),
        (24, 512) => Ok(size_of::<ConcurrentMerkleTree<24, 512>>()),
        (24, 1024) => Ok(size_of::<ConcurrentMerkleTree<24, 1024>>()),
        (24, 2048) => Ok(size_of::<ConcurrentMerkleTree<24, 2048>>()),
        (26, 512) => Ok(size_of::<ConcurrentMerkleTree<26, 512>>()),
        (26, 1024) => Ok(size_of::<ConcurrentMerkleTree<26, 1024>>()),
        (26, 2048) => Ok(size_of::<ConcurrentMerkleTree<26, 2048>>()),
        (30, 512) => Ok(size_of::<ConcurrentMerkleTree<30, 512>>()),
        (30, 1024) => Ok(size_of::<ConcurrentMerkleTree<30, 1024>>()),
        (30, 2048) => Ok(size_of::<ConcurrentMerkleTree<30, 2048>>()),
        _ => {
            msg!(
                "Failed to get size of max depth {} and max buffer size {}",
                max_depth,
                max_buffer_size
            );
            err!(AccountCompressionError::ConcurrentMerkleTreeConstantsError)
        }
    };
    msg!(
        "Got size {} of max depth {} and max buffer size {}",
        data.as_ref().unwrap_or(&0),
        max_depth,
        max_buffer_size
    );
    data
}
pub fn assert_program_authority(
    program: AccountInfo,
    program_data: AccountInfo,
    signer_address: &Pubkey,
) -> Result<()> {
    let program = UpgradeableLoaderState::try_deserialize(&mut program.data.borrow().as_ref())?;
    match program {
        UpgradeableLoaderState::Program {
            programdata_address,
        } => {
            if programdata_address.eq(program_data.key) {
                let programdata = UpgradeableLoaderState::try_deserialize(
                    &mut program_data.data.borrow().as_ref(),
                )?;
                match programdata {
                    UpgradeableLoaderState::ProgramData {
                        slot: _,
                        upgrade_authority_address,
                    } => {
                        if upgrade_authority_address.is_none()
                            || upgrade_authority_address.unwrap().ne(signer_address)
                        {
                            return Err(ErrorCode::Unauthorized.into());
                        }
                    }
                    _ => return Err(ErrorCode::InvalidProgram.into()),
                }
            } else {
                return Err(ErrorCode::InvalidProgram.into());
            }
        }
        _ => return Err(ErrorCode::InvalidProgram.into()),
    }
    return Ok(());
}
pub fn assert_auth_driver<'info>(
    public_info: &Account<'info, PublicInfo>,
    authority: &Signer<'info>,
) -> Result<()> {
    let auth_driver_key = public_info.info.get("auth_driver_key").unwrap();
    let Info::SingleValue {
        value: auth_driver_key_str,
    } = auth_driver_key;
    if authority.key.to_string().ne(auth_driver_key_str) {
        return Err(ErrorCode::Unauthorized.into());
    }
    Ok(())
}