use crate::config::Config;
use crate::error::ButterflyBotError;
use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SolanaRpcProvider {
    QuickNode,
    Helius,
    Custom,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ComputeBudgetPolicy {
    pub unit_limit: u32,
    pub unit_price_microlamports: u64,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SimulationPolicy {
    pub enabled: bool,
    pub commitment: String,
    pub replace_recent_blockhash: bool,
    pub sig_verify: bool,
    pub min_context_slot: Option<u64>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SendPolicy {
    pub skip_preflight: bool,
    pub preflight_commitment: String,
    pub max_retries: usize,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SolanaRpcExecutionPolicy {
    pub provider: SolanaRpcProvider,
    pub endpoint: Option<String>,
    pub commitment: String,
    pub compute_budget: ComputeBudgetPolicy,
    pub simulation: SimulationPolicy,
    pub send: SendPolicy,
}

impl SolanaRpcExecutionPolicy {
    pub fn default_for_provider(provider: SolanaRpcProvider) -> Self {
        match provider {
            SolanaRpcProvider::QuickNode => Self {
                provider,
                endpoint: None,
                commitment: "confirmed".to_string(),
                compute_budget: ComputeBudgetPolicy {
                    unit_limit: 300_000,
                    unit_price_microlamports: 2_500,
                },
                simulation: SimulationPolicy {
                    enabled: true,
                    commitment: "processed".to_string(),
                    replace_recent_blockhash: true,
                    sig_verify: false,
                    min_context_slot: None,
                },
                send: SendPolicy {
                    skip_preflight: false,
                    preflight_commitment: "confirmed".to_string(),
                    max_retries: 5,
                },
            },
            SolanaRpcProvider::Helius => Self {
                provider,
                endpoint: None,
                commitment: "confirmed".to_string(),
                compute_budget: ComputeBudgetPolicy {
                    unit_limit: 350_000,
                    unit_price_microlamports: 5_000,
                },
                simulation: SimulationPolicy {
                    enabled: true,
                    commitment: "processed".to_string(),
                    replace_recent_blockhash: false,
                    sig_verify: false,
                    min_context_slot: None,
                },
                send: SendPolicy {
                    skip_preflight: false,
                    preflight_commitment: "confirmed".to_string(),
                    max_retries: 8,
                },
            },
            SolanaRpcProvider::Custom => Self {
                provider,
                endpoint: None,
                commitment: "confirmed".to_string(),
                compute_budget: ComputeBudgetPolicy {
                    unit_limit: 300_000,
                    unit_price_microlamports: 3_000,
                },
                simulation: SimulationPolicy {
                    enabled: true,
                    commitment: "processed".to_string(),
                    replace_recent_blockhash: true,
                    sig_verify: false,
                    min_context_slot: None,
                },
                send: SendPolicy {
                    skip_preflight: false,
                    preflight_commitment: "confirmed".to_string(),
                    max_retries: 5,
                },
            },
        }
    }

    pub fn from_config(config: &Config) -> Result<Self, ButterflyBotError> {
        let Some(tools) = config.tools.as_ref() else {
            return Ok(Self::default_for_provider(SolanaRpcProvider::QuickNode));
        };
        Self::from_tools(tools)
    }

    pub fn from_tools(tools: &Value) -> Result<Self, ButterflyBotError> {
        let rpc_value = tools
            .get("settings")
            .and_then(|settings| settings.get("solana"))
            .and_then(|solana| solana.get("rpc"));

        let Some(rpc) = rpc_value else {
            return Ok(Self::default_for_provider(SolanaRpcProvider::QuickNode));
        };

        let provider = parse_provider(rpc.get("provider"))?;
        let mut policy = Self::default_for_provider(provider);

        if let Some(endpoint) = rpc.get("endpoint") {
            let endpoint_value = endpoint.as_str().ok_or_else(|| {
                ButterflyBotError::Config(
                    "tools.settings.solana.rpc.endpoint must be a string".to_string(),
                )
            })?;
            if !endpoint_value.trim().is_empty() {
                policy.endpoint = Some(endpoint_value.to_string());
            }
        }

        if let Some(commitment) = rpc.get("commitment") {
            policy.commitment = normalize_commitment(commitment)?;
        }

        if let Some(compute_budget) = rpc.get("compute_budget") {
            let compute_budget_obj = compute_budget.as_object().ok_or_else(|| {
                ButterflyBotError::Config(
                    "tools.settings.solana.rpc.compute_budget must be an object".to_string(),
                )
            })?;

            if let Some(unit_limit) = compute_budget_obj.get("unit_limit") {
                let unit_limit_value = unit_limit.as_u64().ok_or_else(|| {
                    ButterflyBotError::Config(
                        "tools.settings.solana.rpc.compute_budget.unit_limit must be an integer"
                            .to_string(),
                    )
                })?;
                policy.compute_budget.unit_limit = normalize_unit_limit(unit_limit_value)?;
            }

            if let Some(unit_price) = compute_budget_obj.get("unit_price_microlamports") {
                let unit_price_value = unit_price.as_u64().ok_or_else(|| {
                    ButterflyBotError::Config("tools.settings.solana.rpc.compute_budget.unit_price_microlamports must be an integer".to_string())
                })?;
                policy.compute_budget.unit_price_microlamports =
                    normalize_unit_price_microlamports(unit_price_value)?;
            }
        }

        if let Some(simulation) = rpc.get("simulation") {
            let simulation_obj = simulation.as_object().ok_or_else(|| {
                ButterflyBotError::Config(
                    "tools.settings.solana.rpc.simulation must be an object".to_string(),
                )
            })?;

            if let Some(enabled) = simulation_obj.get("enabled") {
                policy.simulation.enabled = enabled.as_bool().ok_or_else(|| {
                    ButterflyBotError::Config(
                        "tools.settings.solana.rpc.simulation.enabled must be a boolean"
                            .to_string(),
                    )
                })?;
            }

            if let Some(commitment) = simulation_obj.get("commitment") {
                policy.simulation.commitment = normalize_commitment(commitment)?;
            }

            if let Some(replace_recent_blockhash) = simulation_obj.get("replace_recent_blockhash") {
                policy.simulation.replace_recent_blockhash = replace_recent_blockhash
                    .as_bool()
                    .ok_or_else(|| {
                        ButterflyBotError::Config(
                            "tools.settings.solana.rpc.simulation.replace_recent_blockhash must be a boolean".to_string(),
                        )
                    })?;
            }

            if let Some(sig_verify) = simulation_obj.get("sig_verify") {
                policy.simulation.sig_verify = sig_verify.as_bool().ok_or_else(|| {
                    ButterflyBotError::Config(
                        "tools.settings.solana.rpc.simulation.sig_verify must be a boolean"
                            .to_string(),
                    )
                })?;
            }

            if let Some(min_context_slot) = simulation_obj.get("min_context_slot") {
                if min_context_slot.is_null() {
                    policy.simulation.min_context_slot = None;
                } else {
                    policy.simulation.min_context_slot =
                        Some(min_context_slot.as_u64().ok_or_else(|| {
                            ButterflyBotError::Config("tools.settings.solana.rpc.simulation.min_context_slot must be an integer".to_string())
                        })?);
                }
            }
        }

        if let Some(send) = rpc.get("send") {
            let send_obj = send.as_object().ok_or_else(|| {
                ButterflyBotError::Config(
                    "tools.settings.solana.rpc.send must be an object".to_string(),
                )
            })?;

            if let Some(skip_preflight) = send_obj.get("skip_preflight") {
                policy.send.skip_preflight = skip_preflight.as_bool().ok_or_else(|| {
                    ButterflyBotError::Config(
                        "tools.settings.solana.rpc.send.skip_preflight must be a boolean"
                            .to_string(),
                    )
                })?;
            }

            if let Some(preflight_commitment) = send_obj.get("preflight_commitment") {
                policy.send.preflight_commitment = normalize_commitment(preflight_commitment)?;
            }

            if let Some(max_retries) = send_obj.get("max_retries") {
                let max_retries_value = max_retries.as_u64().ok_or_else(|| {
                    ButterflyBotError::Config(
                        "tools.settings.solana.rpc.send.max_retries must be an integer".to_string(),
                    )
                })?;
                policy.send.max_retries = normalize_max_retries(max_retries_value)?;
            }
        }

        Ok(policy)
    }
}

fn parse_provider(value: Option<&Value>) -> Result<SolanaRpcProvider, ButterflyBotError> {
    let provider_value = value
        .and_then(|entry| entry.as_str())
        .unwrap_or("quicknode")
        .to_ascii_lowercase();

    match provider_value.as_str() {
        "quicknode" => Ok(SolanaRpcProvider::QuickNode),
        "helius" => Ok(SolanaRpcProvider::Helius),
        "custom" => Ok(SolanaRpcProvider::Custom),
        _ => Err(ButterflyBotError::Config(
            "tools.settings.solana.rpc.provider must be one of quicknode, helius, custom"
                .to_string(),
        )),
    }
}

fn normalize_commitment(value: &Value) -> Result<String, ButterflyBotError> {
    let commitment = value
        .as_str()
        .ok_or_else(|| ButterflyBotError::Config("commitment must be a string".to_string()))?;

    let normalized = commitment.to_ascii_lowercase();
    match normalized.as_str() {
        "processed" | "confirmed" | "finalized" => Ok(normalized),
        _ => Err(ButterflyBotError::Config(
            "commitment must be one of processed, confirmed, finalized".to_string(),
        )),
    }
}

fn normalize_unit_limit(value: u64) -> Result<u32, ButterflyBotError> {
    if value == 0 {
        return Err(ButterflyBotError::Config(
            "compute_budget.unit_limit must be greater than zero".to_string(),
        ));
    }
    let clamped = value.clamp(200_000, 1_400_000);
    Ok(clamped as u32)
}

fn normalize_unit_price_microlamports(value: u64) -> Result<u64, ButterflyBotError> {
    if value == 0 {
        return Err(ButterflyBotError::Config(
            "compute_budget.unit_price_microlamports must be greater than zero".to_string(),
        ));
    }
    Ok(value.clamp(1, 1_000_000))
}

fn normalize_max_retries(value: u64) -> Result<usize, ButterflyBotError> {
    if value > 100 {
        return Err(ButterflyBotError::Config(
            "send.max_retries must be less than or equal to 100".to_string(),
        ));
    }
    Ok(value as usize)
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn quicknode_and_helius_have_distinct_defaults() {
        let quicknode =
            SolanaRpcExecutionPolicy::default_for_provider(SolanaRpcProvider::QuickNode);
        let helius = SolanaRpcExecutionPolicy::default_for_provider(SolanaRpcProvider::Helius);

        assert_ne!(
            quicknode.compute_budget.unit_price_microlamports,
            helius.compute_budget.unit_price_microlamports
        );
        assert_ne!(
            quicknode.simulation.replace_recent_blockhash,
            helius.simulation.replace_recent_blockhash
        );
        assert_ne!(quicknode.send.max_retries, helius.send.max_retries);
    }

    #[test]
    fn from_tools_applies_overrides_with_normalization() {
        let tools = json!({
            "settings": {
                "solana": {
                    "rpc": {
                        "provider": "helius",
                        "endpoint": "https://example.invalid/rpc",
                        "commitment": "Finalized",
                        "compute_budget": {
                            "unit_limit": 5_000_000,
                            "unit_price_microlamports": 9_999_999
                        },
                        "simulation": {
                            "enabled": true,
                            "commitment": "Processed",
                            "replace_recent_blockhash": true,
                            "sig_verify": false,
                            "min_context_slot": 42
                        },
                        "send": {
                            "skip_preflight": false,
                            "preflight_commitment": "confirmed",
                            "max_retries": 20
                        }
                    }
                }
            }
        });

        let policy = SolanaRpcExecutionPolicy::from_tools(&tools).unwrap();
        assert_eq!(policy.provider, SolanaRpcProvider::Helius);
        assert_eq!(
            policy.endpoint.as_deref(),
            Some("https://example.invalid/rpc")
        );
        assert_eq!(policy.commitment, "finalized");
        assert_eq!(policy.compute_budget.unit_limit, 1_400_000);
        assert_eq!(policy.compute_budget.unit_price_microlamports, 1_000_000);
        assert_eq!(policy.simulation.commitment, "processed");
        assert_eq!(policy.simulation.min_context_slot, Some(42));
        assert_eq!(policy.send.max_retries, 20);
    }

    #[test]
    fn from_tools_rejects_invalid_commitment() {
        let tools = json!({
            "settings": {
                "solana": {
                    "rpc": {
                        "commitment": "recent"
                    }
                }
            }
        });

        let err = SolanaRpcExecutionPolicy::from_tools(&tools).unwrap_err();
        assert!(err
            .to_string()
            .contains("commitment must be one of processed, confirmed, finalized"));
    }

    #[test]
    fn defaults_to_quicknode_when_missing_config() {
        let tools = json!({"settings": {"permissions": {"default_deny": true}}});
        let policy = SolanaRpcExecutionPolicy::from_tools(&tools).unwrap();
        assert_eq!(policy.provider, SolanaRpcProvider::QuickNode);
    }
}
