use anchor_lang::prelude::*;
use crate::{
    errors::AccountCompressionErrorCode,
    initialize_address_merkle_tree::process_initialize_address_merkle_tree,
    initialize_address_queue::process_initialize_address_queue,
    state::QueueAccount,
    utils::{
        check_account::check_account_balance_is_rent_exempt,
        check_signer_is_registered_or_authority::{
            check_signer_is_registered_or_authority, GroupAccess, GroupAccounts,
        },
        constants::{
            ADDRESS_MERKLE_TREE_CANOPY_DEPTH, ADDRESS_MERKLE_TREE_CHANGELOG,
            ADDRESS_MERKLE_TREE_HEIGHT, ADDRESS_MERKLE_TREE_INDEXED_CHANGELOG,
            ADDRESS_MERKLE_TREE_ROOTS,
        },
    },
    AddressMerkleTreeAccount, NullifierQueueConfig, RegisteredProgram, SAFETY_MARGIN,
};
#[derive(Debug, Clone, AnchorDeserialize, AnchorSerialize, PartialEq)]
pub struct AddressMerkleTreeConfig {
    pub height: u32,
    pub changelog_size: u64,
    pub roots_size: u64,
    pub canopy_depth: u64,
    pub address_changelog_size: u64,
    pub network_fee: Option<u64>,
    pub rollover_threshold: Option<u64>,
    pub close_threshold: Option<u64>,
}
impl Default for AddressMerkleTreeConfig {
    fn default() -> Self {
        Self {
            height: ADDRESS_MERKLE_TREE_HEIGHT as u32,
            changelog_size: ADDRESS_MERKLE_TREE_CHANGELOG,
            roots_size: ADDRESS_MERKLE_TREE_ROOTS,
            canopy_depth: ADDRESS_MERKLE_TREE_CANOPY_DEPTH,
            address_changelog_size: ADDRESS_MERKLE_TREE_INDEXED_CHANGELOG,
            network_fee: Some(5000),
            rollover_threshold: Some(95),
            close_threshold: None,
        }
    }
}
pub type AddressQueueConfig = NullifierQueueConfig;
#[derive(Accounts)]
pub struct InitializeAddressMerkleTreeAndQueue<'info> {
    #[account(mut)]
    pub authority: Signer<'info>,
    #[account(zero)]
    pub merkle_tree: AccountLoader<'info, AddressMerkleTreeAccount>,
    #[account(zero)]
    pub queue: AccountLoader<'info, QueueAccount>,
    pub registered_program_pda: Option<Account<'info, RegisteredProgram>>,
}
impl<'info> GroupAccounts<'info> for InitializeAddressMerkleTreeAndQueue<'info> {
    fn get_authority(&self) -> &Signer<'info> {
        &self.authority
    }
    fn get_registered_program_pda(&self) -> &Option<Account<'info, RegisteredProgram>> {
        &self.registered_program_pda
    }
}
impl GroupAccess for RegisteredProgram {
    fn get_owner(&self) -> &Pubkey {
        &self.group_authority_pda
    }
    fn get_program_owner(&self) -> &Pubkey {
        &self.registered_program_id
    }
}
pub fn process_initialize_address_merkle_tree_and_queue<'info>(
    ctx: Context<'_, '_, '_, 'info, InitializeAddressMerkleTreeAndQueue<'info>>,
    index: u64,
    program_owner: Option<Pubkey>,
    forester: Option<Pubkey>,
    merkle_tree_config: AddressMerkleTreeConfig,
    queue_config: AddressQueueConfig,
) -> Result<()> {
    if merkle_tree_config.height as u64 != ADDRESS_MERKLE_TREE_HEIGHT {
        msg!(
            "Unsupported Merkle tree height: {}. The only currently supported height is: {}",
            merkle_tree_config.height,
            ADDRESS_MERKLE_TREE_HEIGHT
        );
        return err!(AccountCompressionErrorCode::UnsupportedHeight);
    }
    if merkle_tree_config.canopy_depth != ADDRESS_MERKLE_TREE_CANOPY_DEPTH {
        msg!(
            "Unsupported canopy depth: {}. The only currently supported depth is: {}",
            merkle_tree_config.canopy_depth,
            ADDRESS_MERKLE_TREE_CANOPY_DEPTH
        );
        return err!(AccountCompressionErrorCode::UnsupportedCanopyDepth);
    }
    if merkle_tree_config.close_threshold.is_some() {
        msg!("close_threshold is not supported yet");
        return err!(AccountCompressionErrorCode::UnsupportedCloseThreshold);
    }
    let minimum_sequence_threshold = merkle_tree_config.roots_size + SAFETY_MARGIN;
    if queue_config.sequence_threshold < minimum_sequence_threshold {
        msg!(
            "Invalid sequence threshold: {}. Should be at least {}",
            queue_config.sequence_threshold,
            minimum_sequence_threshold
        );
        return err!(AccountCompressionErrorCode::InvalidSequenceThreshold);
    }
    let owner = match ctx.accounts.registered_program_pda.as_ref() {
        Some(registered_program_pda) => {
            check_signer_is_registered_or_authority::<
                InitializeAddressMerkleTreeAndQueue,
                RegisteredProgram,
            >(&ctx, registered_program_pda)?;
            registered_program_pda.group_authority_pda
        }
        None => ctx.accounts.authority.key(),
    };
    let merkle_tree_expected_size = AddressMerkleTreeAccount::size(
        merkle_tree_config.height as usize,
        merkle_tree_config.changelog_size as usize,
        merkle_tree_config.roots_size as usize,
        merkle_tree_config.canopy_depth as usize,
        merkle_tree_config.address_changelog_size as usize,
    );
    let queue_expected_size = QueueAccount::size(queue_config.capacity as usize)?;
    let merkle_tree_rent = check_account_balance_is_rent_exempt(
        &ctx.accounts.merkle_tree.to_account_info(),
        merkle_tree_expected_size,
    )?;
    check_account_balance_is_rent_exempt(
        &ctx.accounts.queue.to_account_info(),
        queue_expected_size,
    )?;
    process_initialize_address_queue(
        &ctx.accounts.queue.to_account_info(),
        &ctx.accounts.queue,
        index,
        owner,
        program_owner,
        forester,
        ctx.accounts.merkle_tree.key(),
        queue_config.capacity,
        queue_config.sequence_threshold,
        queue_config.network_fee.unwrap_or_default(),
        merkle_tree_config.rollover_threshold,
        merkle_tree_config.close_threshold,
        merkle_tree_config.height,
        merkle_tree_rent,
    )?;
    process_initialize_address_merkle_tree(
        &ctx.accounts.merkle_tree,
        index,
        owner,
        program_owner,
        forester,
        merkle_tree_config.height,
        merkle_tree_config.changelog_size,
        merkle_tree_config.roots_size,
        merkle_tree_config.canopy_depth,
        merkle_tree_config.address_changelog_size,
        ctx.accounts.queue.key(),
        merkle_tree_config.network_fee.unwrap_or_default(),
        merkle_tree_config.rollover_threshold,
        merkle_tree_config.close_threshold,
    )
}