use cosmwasm_std::{
    to_binary, Addr, Binary, Coin, CosmosMsg, Decimal, Deps, StdError, StdResult, Uint128, WasmMsg,
    WasmQuery,
};

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::msg::{ExecuteMsg, QueryMsg};

/// The base gas that the beacon contract consumes.
pub const BEACON_BASE_GAS: u64 = 275_000;

/// Message for updating the configuration of the beacon contract
/// This has can only be called by the owner of the contract
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct UpdateConfigMsg {
    ///The amount of tokens that must be deposited to whitelist a new public key.
    pub whitelist_deposit_amt: Uint128,
    ///The time, in blocks, before a whitelisted public key can be used to submit entropy.
    pub key_activation_delay: u64,
    ///The amount of the deposit that unlocks with each submission of entropy.
    pub refund_increment_amt: Uint128,
    ///The fee that the protocol contract charges on top of the requested gas fees.
    pub protocol_fee: u64,
    ///The share of the protocol fee that is distributed to the wallet submitting entropy.
    pub submitter_share: u64,
    ///Whether or not the contract is in permissioned mode.
    pub permissioned: bool,
    ///Whether or not the beacon has been paused.
    pub paused: bool,
}

/// The struct that is used to represent entropy requests to the Beacon contract.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct RequestEntropyMsg {
    /// The amount of gas that has been provisioned for the callback message.
    pub callback_gas_limit: u64,
    /// The address to send the callback message to.
    pub callback_address: Addr,
    /// Arbitrary data to be included in the callback message.
    /// Can be decoded by the callback contract.
    pub callback_msg: Binary,
}

/// The struct that represents callbacks or "responses" from the Beacon contract.
/// Contains both the callback message specified in the request, and the entropy
/// that was returned generated by the network.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct EntropyCallbackMsg {
    /// The entropy that was generated by the network, in a byte array.
    /// The length of the byte array is 64 elements, meaning that there are
    /// 64 total bytes of entropy.
    pub entropy: Vec<u8>,
    /// The original address that submitted the request for entropy. This
    /// should be used to verify that the callback message was generated by
    /// the requester, or by a trusted other contract.
    pub requester: Addr,
    /// The callback message that was specified in the request. The structure
    /// of this Binary is unknown by the Beacon contract, and it is up to your
    /// contract to correctly decode it.
    pub msg: Binary,
}

#[cfg(feature = "ecvrf")]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ReceiverExecuteMsg {
    ReceiveEntropy(EntropyCallbackMsg),
}

#[cfg(feature = "ecvrf")]
impl EntropyCallbackMsg {
    pub fn into_binary(self) -> StdResult<Binary> {
        let msg = ReceiverExecuteMsg::ReceiveEntropy(self);
        to_binary(&msg)
    }
    pub fn into_cosmos_msg<T: Into<String>>(self, contract_addr: T) -> StdResult<CosmosMsg> {
        let msg = self.into_binary()?;
        let execute = WasmMsg::Execute {
            contract_addr: contract_addr.into(),
            msg,
            funds: vec![],
        };
        Ok(execute.into())
    }
}

/// A convinience struct that smoothens the process of creating a request for
/// entropy from the Beacon contract.
///
/// # Example
/// ```
/// use cosmwasm_std::{Addr, Coin, Response, Uint128};
/// use serde::{Deserialize, Serialize};
/// use entropy_beacon_cosmos::EntropyRequest;
///
/// #[derive(Serialize, Deserialize, Clone, Debug)]
/// struct ExampleResponseStruct{
///    example_field: String,
/// }
/// # fn execute() -> Result<Response, Box<dyn std::error::Error>> {
/// // Then, in our execute function, we can create a request for entropy:
/// let request = EntropyRequest {
///     callback_gas_limit: 100,
///     callback_address: Addr::unchecked("example_contract_address"),
///     funds: vec![Coin{denom: "uluna".to_string(), amount: Uint128::from(1000u128)}],
///     callback_msg: ExampleResponseStruct{
///         example_field: "example_value".to_string(),
///     },
/// };
/// // When submitting the request to the Beacon, we append it as a message to our response:
/// let beacon_addr = Addr::unchecked("beacon_contract_address");
/// Ok(Response::new().add_message(request.into_cosmos(beacon_addr)?))
/// # }
/// ```
pub struct EntropyRequest<T>
where
    T: Serialize,
{
    /// The amount of gas that has been provisioned for the callback message.
    pub callback_gas_limit: u64,
    /// The address to send the callback message to.
    pub callback_address: Addr,
    /// The funds to be sent alongside the request - to provide callback gas and other fees.
    pub funds: Vec<Coin>,
    /// Arbitrary data to be included in the callback message.
    pub callback_msg: T,
}
impl<T> EntropyRequest<T>
where
    T: Serialize,
{
    /// Creates a CosmosMsg that will execute the request on the Beacon contract,
    /// whose address is provided as the first argument.
    pub fn into_cosmos(self, beacon_addr: Addr) -> Result<CosmosMsg, StdError> {
        Ok(CosmosMsg::Wasm(WasmMsg::Execute {
            contract_addr: beacon_addr.to_string(),
            msg: to_binary(&ExecuteMsg::RequestEntropy(RequestEntropyMsg {
                callback_gas_limit: self.callback_gas_limit,
                callback_address: self.callback_address,
                callback_msg: to_binary(&self.callback_msg)?,
            }))?,
            funds: self.funds,
        }))
    }
}

/// Query the beacon contract for the estimated conversion of gas to coins.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct CalculateFeeQuery {
    /// The amount of gas that has been requested for the callback message.
    pub callback_gas_limit: u64,
}

impl CalculateFeeQuery {
    /// Queries the beacon contract for the estimated fee required to pay
    /// for a callback message with the specified gas limit.
    pub fn query(deps: Deps, callback_gas_limit: u64, beacon_addr: Addr) -> StdResult<u64> {
        Ok(deps
            .querier
            .query::<CalculateFeeResponse>(&cosmwasm_std::QueryRequest::Wasm(WasmQuery::Smart {
                contract_addr: beacon_addr.to_string(),
                msg: to_binary(&QueryMsg::CalculateFee(CalculateFeeQuery {
                    callback_gas_limit,
                }))?,
            }))?
            .fee)
    }
}

/// Response from the beacon contract for the estimated conversion of gas to coins.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct CalculateFeeResponse {
    /// The amount of coin that will be required to pay for the callback message.
    pub fee: u64,
    /// The gas price that was used to calculate the fee.
    pub gas_price: Decimal,
}
