use solana_sdk::compute_budget::ComputeBudgetInstruction;
use solana_sdk::instruction::Instruction;

use anchor_lang::prelude::{AccountMeta, Pubkey};
use anyhow::Context;

/// Some Result<> types don't convert to anyhow::Result nicely. Force them through stringification.
pub trait AnyhowWrap {
    type Value;
    fn map_err_anyhow(self) -> anyhow::Result<Self::Value>;
}

impl<T, E: std::fmt::Debug> AnyhowWrap for Result<T, E> {
    type Value = T;
    fn map_err_anyhow(self) -> anyhow::Result<Self::Value> {
        self.map_err(|err| anyhow::anyhow!("{:?}", err))
    }
}

/// Push to an async_channel::Sender and ignore if the channel is full
pub trait AsyncChannelSendUnlessFull<T> {
    /// Send a message if the channel isn't full
    fn send_unless_full(&self, msg: T) -> anyhow::Result<()>;
}

impl<T> AsyncChannelSendUnlessFull<T> for async_channel::Sender<T> {
    fn send_unless_full(&self, msg: T) -> anyhow::Result<()> {
        use async_channel::*;
        match self.try_send(msg) {
            Ok(()) => Ok(()),
            Err(TrySendError::Closed(_)) => Err(anyhow::format_err!("channel is closed")),
            Err(TrySendError::Full(_)) => Ok(()),
        }
    }
}
impl<T> AsyncChannelSendUnlessFull<T> for tokio::sync::mpsc::Sender<T> {
    fn send_unless_full(&self, msg: T) -> anyhow::Result<()> {
        use tokio::sync::mpsc::*;
        match self.try_send(msg) {
            Ok(()) => Ok(()),
            Err(error::TrySendError::Closed(_)) => Err(anyhow::format_err!("channel is closed")),
            Err(error::TrySendError::Full(_)) => Ok(()),
        }
    }
}

/// Like tokio::time::interval(), but with Delay as default MissedTickBehavior
///
/// The default (Burst) means that if the time between tick() calls is longer
/// than `period` there'll be a burst of catch-up ticks.
///
/// This Interval guarantees that when tick() returns, at least `period` will have
/// elapsed since the last return. That way it's more appropriate for jobs that
/// don't need to catch up.
pub fn delay_interval(period: std::time::Duration) -> tokio::time::Interval {
    let mut interval = tokio::time::interval(period);
    interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
    interval
}

/// Convenience function used in binaries to set up the fmt tracing_subscriber,
/// with cololring enabled only if logging to a terminal and with EnvFilter.
pub fn tracing_subscriber_init() {
    let format = tracing_subscriber::fmt::format().with_ansi(atty::is(atty::Stream::Stdout));

    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        .event_format(format)
        .init();
}

pub async fn http_error_handling<T: serde::de::DeserializeOwned>(
    response: reqwest::Response,
) -> anyhow::Result<T> {
    let status = response.status();
    let response_text = response
        .text()
        .await
        .context("awaiting body of http request")?;
    if !status.is_success() {
        anyhow::bail!("http request failed, status: {status}, body: {response_text}");
    }
    serde_json::from_str::<T>(&response_text)
        .with_context(|| format!("response has unexpected format, body: {response_text}"))
}

pub fn to_readonly_account_meta(pubkey: Pubkey) -> AccountMeta {
    AccountMeta {
        pubkey,
        is_writable: false,
        is_signer: false,
    }
}

pub fn to_writable_account_meta(pubkey: Pubkey) -> AccountMeta {
    AccountMeta {
        pubkey,
        is_writable: true,
        is_signer: false,
    }
}

#[derive(Default, Clone)]
pub struct PreparedInstructions {
    pub instructions: Vec<Instruction>,
    pub cu: u32,
}

impl PreparedInstructions {
    pub fn new() -> Self {
        Self {
            instructions: vec![],
            cu: 0,
        }
    }

    pub fn from_vec(instructions: Vec<Instruction>, cu: u32) -> Self {
        Self { instructions, cu }
    }

    pub fn from_single(instruction: Instruction, cu: u32) -> Self {
        Self {
            instructions: vec![instruction],
            cu,
        }
    }

    pub fn push(&mut self, ix: Instruction, cu: u32) {
        self.instructions.push(ix);
        self.cu += cu;
    }

    pub fn append(&mut self, mut other: Self) {
        self.instructions.append(&mut other.instructions);
        self.cu += other.cu;
    }

    pub fn to_instructions(self) -> Vec<Instruction> {
        let mut ixs = self.instructions;
        ixs.insert(0, ComputeBudgetInstruction::set_compute_unit_limit(self.cu));
        ixs
    }

    pub fn is_empty(&self) -> bool {
        self.instructions.is_empty()
    }

    pub fn clear(&mut self) {
        self.instructions.clear();
        self.cu = 0;
    }

    pub fn len(&self) -> usize {
        self.instructions.len()
    }
}
