use anchor_lang::{
    prelude::*,
    solana_program::{
        instruction::Instruction,
        program::{get_return_data, invoke_signed},
    },
    AnchorDeserialize, InstructionData,
};
use clockwork_network_program::state::{Fee, Pool, Worker, WorkerAccount};
use clockwork_utils::thread::{SerializableInstruction, ThreadResponse, PAYER_PUBKEY};
use crate::{errors::ClockworkError, state::*};
const POOL_ID: u64 = 0;
const TRANSACTION_BASE_FEE_REIMBURSEMENT: u64 = 5_000;
#[derive(Accounts)]
pub struct ThreadExec<'info> {
    #[account(
        mut,
        seeds = [
            clockwork_network_program::state::SEED_FEE,
            worker.key().as_ref(),
        ],
        bump,
        seeds::program = clockwork_network_program::ID,
        has_one = worker,
    )]
    pub fee: Account<'info, Fee>,
    #[account(address = Pool::pubkey(POOL_ID))]
    pub pool: Box<Account<'info, Pool>>,
    #[account(mut)]
    pub signatory: Signer<'info>,
    #[account(
        mut,
        seeds = [
            SEED_THREAD,
            thread.authority.as_ref(),
            thread.id.as_slice(),
        ],
        bump = thread.bump,
        constraint = !thread.paused @ ClockworkError::ThreadPaused,
        constraint = thread.next_instruction.is_some(),
        constraint = thread.exec_context.is_some()
    )]
    pub thread: Box<Account<'info, Thread>>,
    #[account(address = worker.pubkey())]
    pub worker: Account<'info, Worker>,
}
pub fn handler(ctx: Context<ThreadExec>) -> Result<()> {
    let clock = Clock::get().unwrap();
    let fee = &mut ctx.accounts.fee;
    let pool = &ctx.accounts.pool;
    let signatory = &mut ctx.accounts.signatory;
    let thread = &mut ctx.accounts.thread;
    let worker = &ctx.accounts.worker;
    if thread.exec_context.unwrap().last_exec_at == clock.slot
        && thread.exec_context.unwrap().execs_since_slot >= thread.rate_limit
    {
        return Err(ClockworkError::RateLimitExeceeded.into());
    }
    let signatory_lamports_pre = signatory.lamports();
    let instruction: &mut SerializableInstruction = &mut thread.next_instruction.clone().unwrap();
    for acc in instruction.accounts.iter_mut() {
        if acc.pubkey.eq(&PAYER_PUBKEY) {
            acc.pubkey = signatory.key();
        }
    }
    invoke_signed(
        &Instruction::from(&*instruction),
        ctx.remaining_accounts,
        &[&[
            SEED_THREAD,
            thread.authority.as_ref(),
            thread.id.as_slice(),
            &[thread.bump],
        ]],
    )?;
    require!(signatory.data_is_empty(), ClockworkError::UnauthorizedWrite);
    let thread_response: Option<ThreadResponse> = match get_return_data() {
        None => None,
        Some((program_id, return_data)) => {
            require!(
                program_id.eq(&instruction.program_id),
                ClockworkError::InvalidThreadResponse
            );
            ThreadResponse::try_from_slice(return_data.as_slice()).ok()
        }
    };
    let mut close_to = None;
    let mut next_instruction = None;
    if let Some(thread_response) = thread_response {
        close_to = thread_response.close_to;
        next_instruction = thread_response.dynamic_instruction;
        if let Some(trigger) = thread_response.trigger {
            require!(
                std::mem::discriminant(&thread.trigger) == std::mem::discriminant(&trigger),
                ClockworkError::InvalidTriggerVariant
            );
            thread.trigger = trigger.clone();
            thread.exec_context = Some(ExecContext {
                trigger_context: match trigger {
                    Trigger::Account {
                        address: _,
                        offset: _,
                        size: _,
                    } => TriggerContext::Account { data_hash: 0 },
                    _ => thread.exec_context.unwrap().trigger_context,
                },
                ..thread.exec_context.unwrap()
            })
        }
    }
    let mut exec_index = thread.exec_context.unwrap().exec_index;
    if next_instruction.is_none() {
        if let Some(ix) = thread.instructions.get((exec_index + 1) as usize) {
            next_instruction = Some(ix.clone());
            exec_index = exec_index + 1;
        }
    }
    if let Some(close_to) = close_to {
        thread.next_instruction = Some(
            Instruction {
                program_id: crate::ID,
                accounts: crate::accounts::ThreadDelete {
                    authority: thread.key(),
                    close_to,
                    thread: thread.key(),
                }
                .to_account_metas(Some(true)),
                data: crate::instruction::ThreadDelete {}.data(),
            }
            .into(),
        );
    } else {
        thread.next_instruction = next_instruction;
    }
    thread.exec_context = Some(ExecContext {
        exec_index,
        execs_since_reimbursement: thread
            .exec_context
            .unwrap()
            .execs_since_reimbursement
            .checked_add(1)
            .unwrap(),
        execs_since_slot: if clock.slot == thread.exec_context.unwrap().last_exec_at {
            thread
                .exec_context
                .unwrap()
                .execs_since_slot
                .checked_add(1)
                .unwrap()
        } else {
            1
        },
        last_exec_at: clock.slot,
        ..thread.exec_context.unwrap()
    });
    thread.realloc()?;
    let signatory_lamports_post = signatory.lamports();
    let signatory_reimbursement = signatory_lamports_pre.saturating_sub(signatory_lamports_post);
    if signatory_reimbursement.gt(&0) {
        **thread.to_account_info().try_borrow_mut_lamports()? = thread
            .to_account_info()
            .lamports()
            .checked_sub(signatory_reimbursement)
            .unwrap();
        **signatory.to_account_info().try_borrow_mut_lamports()? = signatory
            .to_account_info()
            .lamports()
            .checked_add(signatory_reimbursement)
            .unwrap();
    }
    if pool.clone().into_inner().workers.contains(&worker.key()) {
        **thread.to_account_info().try_borrow_mut_lamports()? = thread
            .to_account_info()
            .lamports()
            .checked_sub(thread.fee)
            .unwrap();
        **fee.to_account_info().try_borrow_mut_lamports()? = fee
            .to_account_info()
            .lamports()
            .checked_add(thread.fee)
            .unwrap();
    }
    if thread.next_instruction.is_none()
        || thread.exec_context.unwrap().execs_since_reimbursement >= thread.rate_limit
    {
        **thread.to_account_info().try_borrow_mut_lamports()? = thread
            .to_account_info()
            .lamports()
            .checked_sub(TRANSACTION_BASE_FEE_REIMBURSEMENT)
            .unwrap();
        **signatory.to_account_info().try_borrow_mut_lamports()? = signatory
            .to_account_info()
            .lamports()
            .checked_add(TRANSACTION_BASE_FEE_REIMBURSEMENT)
            .unwrap();
        thread.exec_context = Some(ExecContext {
            execs_since_reimbursement: 0,
            ..thread.exec_context.unwrap()
        });
    }
    Ok(())
}