//! Synor Contract SDK client

use reqwest::Client as HttpClient;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use super::error::{ContractError, Result};
use super::types::*;

/// Contract SDK configuration
#[derive(Debug, Clone)]
pub struct ContractConfig {
    /// API key for authentication
    pub api_key: String,
    /// API endpoint URL
    pub endpoint: String,
    /// Request timeout in milliseconds
    pub timeout_ms: u64,
    /// Number of retry attempts
    pub retries: u32,
}

impl ContractConfig {
    /// Create a new configuration with the given API key
    pub fn new(api_key: impl Into<String>) -> Self {
        Self {
            api_key: api_key.into(),
            endpoint: "https://contract.synor.io".to_string(),
            timeout_ms: 30000,
            retries: 3,
        }
    }

    /// Set the endpoint URL
    pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
        self.endpoint = endpoint.into();
        self
    }

    /// Set the timeout in milliseconds
    pub fn timeout_ms(mut self, timeout_ms: u64) -> Self {
        self.timeout_ms = timeout_ms;
        self
    }

    /// Set the number of retries
    pub fn retries(mut self, retries: u32) -> Self {
        self.retries = retries;
        self
    }
}

/// Synor Contract SDK client
pub struct SynorContract {
    config: ContractConfig,
    client: HttpClient,
    closed: Arc<AtomicBool>,
}

impl SynorContract {
    /// Create a new Contract client
    pub fn new(config: ContractConfig) -> Result<Self> {
        let client = HttpClient::builder()
            .timeout(std::time::Duration::from_millis(config.timeout_ms))
            .build()
            .map_err(|e| ContractError::Request(e.to_string()))?;

        Ok(Self {
            config,
            client,
            closed: Arc::new(AtomicBool::new(false)),
        })
    }

    fn check_closed(&self) -> Result<()> {
        if self.closed.load(Ordering::SeqCst) {
            return Err(ContractError::Closed);
        }
        Ok(())
    }

    async fn request<T: serde::de::DeserializeOwned>(
        &self,
        method: reqwest::Method,
        path: &str,
        body: Option<serde_json::Value>,
    ) -> Result<T> {
        self.check_closed()?;

        let url = format!("{}{}", self.config.endpoint, path);
        let mut last_error = None;

        for attempt in 0..self.config.retries {
            let mut req = self
                .client
                .request(method.clone(), &url)
                .header("Authorization", format!("Bearer {}", self.config.api_key))
                .header("Content-Type", "application/json")
                .header("X-SDK-Version", format!("rust/{}", env!("CARGO_PKG_VERSION")));

            if let Some(ref b) = body {
                req = req.json(b);
            }

            match req.send().await {
                Ok(response) => {
                    let status = response.status();
                    let text = response.text().await.map_err(|e| ContractError::Response(e.to_string()))?;

                    if status.is_success() {
                        return serde_json::from_str(&text).map_err(ContractError::from);
                    }

                    // Try to parse error response
                    if let Ok(error_response) = serde_json::from_str::<serde_json::Value>(&text) {
                        let message = error_response
                            .get("message")
                            .or_else(|| error_response.get("error"))
                            .and_then(|v| v.as_str())
                            .unwrap_or("Unknown error")
                            .to_string();
                        let code = error_response
                            .get("code")
                            .and_then(|v| v.as_str())
                            .map(|s| s.to_string());

                        last_error = Some(ContractError::Api {
                            message,
                            code,
                            status_code: Some(status.as_u16()),
                        });
                    } else {
                        last_error = Some(ContractError::Response(text));
                    }
                }
                Err(e) => {
                    last_error = Some(ContractError::Request(e.to_string()));
                }
            }

            if attempt < self.config.retries - 1 {
                tokio::time::sleep(std::time::Duration::from_millis(
                    2u64.pow(attempt) * 1000,
                ))
                .await;
            }
        }

        Err(last_error.unwrap_or_else(|| ContractError::Request("Unknown error".to_string())))
    }

    async fn get<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T> {
        self.request(reqwest::Method::GET, path, None).await
    }

    async fn post<T: serde::de::DeserializeOwned>(
        &self,
        path: &str,
        body: serde_json::Value,
    ) -> Result<T> {
        self.request(reqwest::Method::POST, path, Some(body)).await
    }

    // ==================== Contract Deployment ====================

    /// Deploy a new smart contract
    pub async fn deploy(&self, options: DeployContractOptions) -> Result<DeploymentResult> {
        let mut body = serde_json::json!({
            "bytecode": options.bytecode,
        });

        if let Some(abi) = &options.abi {
            body["abi"] = serde_json::to_value(abi)?;
        }
        if let Some(args) = &options.constructor_args {
            body["constructor_args"] = serde_json::to_value(args)?;
        }
        if let Some(value) = &options.value {
            body["value"] = serde_json::json!(value);
        }
        if let Some(gas_limit) = options.gas_limit {
            body["gas_limit"] = serde_json::json!(gas_limit);
        }
        if let Some(gas_price) = &options.gas_price {
            body["gas_price"] = serde_json::json!(gas_price);
        }
        if let Some(nonce) = options.nonce {
            body["nonce"] = serde_json::json!(nonce);
        }

        self.post("/contract/deploy", body).await
    }

    /// Deploy a contract using CREATE2 for deterministic addresses
    pub async fn deploy_create2(&self, options: DeployContractOptions, salt: &str) -> Result<DeploymentResult> {
        let mut body = serde_json::json!({
            "bytecode": options.bytecode,
            "salt": salt,
        });

        if let Some(abi) = &options.abi {
            body["abi"] = serde_json::to_value(abi)?;
        }
        if let Some(args) = &options.constructor_args {
            body["constructor_args"] = serde_json::to_value(args)?;
        }
        if let Some(value) = &options.value {
            body["value"] = serde_json::json!(value);
        }
        if let Some(gas_limit) = options.gas_limit {
            body["gas_limit"] = serde_json::json!(gas_limit);
        }
        if let Some(gas_price) = &options.gas_price {
            body["gas_price"] = serde_json::json!(gas_price);
        }

        self.post("/contract/deploy/create2", body).await
    }

    /// Predict the address from a CREATE2 deployment
    pub async fn predict_address(&self, bytecode: &str, salt: &str, deployer: Option<&str>) -> Result<String> {
        let mut body = serde_json::json!({
            "bytecode": bytecode,
            "salt": salt,
        });

        if let Some(d) = deployer {
            body["deployer"] = serde_json::json!(d);
        }

        let response: serde_json::Value = self.post("/contract/predict-address", body).await?;
        response["address"]
            .as_str()
            .map(|s| s.to_string())
            .ok_or_else(|| ContractError::Response("Missing address in response".to_string()))
    }

    // ==================== Contract Interaction ====================

    /// Call a view/pure function (read-only, no gas)
    pub async fn call(&self, options: CallContractOptions) -> Result<serde_json::Value> {
        let body = serde_json::json!({
            "contract": options.contract,
            "method": options.method,
            "args": options.args,
            "abi": options.abi,
        });

        self.post("/contract/call", body).await
    }

    /// Send a state-changing transaction
    pub async fn send(&self, options: SendContractOptions) -> Result<TransactionResult> {
        let mut body = serde_json::json!({
            "contract": options.contract,
            "method": options.method,
            "args": options.args,
            "abi": options.abi,
        });

        if let Some(value) = &options.value {
            body["value"] = serde_json::json!(value);
        }
        if let Some(gas_limit) = options.gas_limit {
            body["gas_limit"] = serde_json::json!(gas_limit);
        }
        if let Some(gas_price) = &options.gas_price {
            body["gas_price"] = serde_json::json!(gas_price);
        }
        if let Some(nonce) = options.nonce {
            body["nonce"] = serde_json::json!(nonce);
        }

        self.post("/contract/send", body).await
    }

    // ==================== Events ====================

    /// Get events from a contract
    pub async fn get_events(&self, filter: EventFilter) -> Result<Vec<DecodedEvent>> {
        let mut body = serde_json::json!({
            "contract": filter.contract,
        });

        if let Some(event) = &filter.event {
            body["event"] = serde_json::json!(event);
        }
        if let Some(from_block) = filter.from_block {
            body["from_block"] = serde_json::json!(from_block);
        }
        if let Some(to_block) = filter.to_block {
            body["to_block"] = serde_json::json!(to_block);
        }
        if let Some(topics) = &filter.topics {
            body["topics"] = serde_json::to_value(topics)?;
        }
        if let Some(abi) = &filter.abi {
            body["abi"] = serde_json::to_value(abi)?;
        }

        self.post("/contract/events", body).await
    }

    /// Get the logs for a contract
    pub async fn get_logs(&self, contract: &str, from_block: Option<u64>, to_block: Option<u64>) -> Result<Vec<EventLog>> {
        let mut params = format!("contract={}", contract);
        if let Some(from) = from_block {
            params.push_str(&format!("&from_block={}", from));
        }
        if let Some(to) = to_block {
            params.push_str(&format!("&to_block={}", to));
        }

        self.get(&format!("/contract/logs?{}", params)).await
    }

    /// Decode event logs using an ABI
    pub async fn decode_logs(&self, logs: &[EventLog], abi: &Abi) -> Result<Vec<DecodedEvent>> {
        let body = serde_json::json!({
            "logs": logs,
            "abi": abi,
        });

        self.post("/contract/decode-logs", body).await
    }

    // ==================== ABI Utilities ====================

    /// Encode a function call
    pub async fn encode_call(&self, options: EncodeCallOptions) -> Result<String> {
        let body = serde_json::json!({
            "method": options.method,
            "args": options.args,
            "abi": options.abi,
        });

        let response: serde_json::Value = self.post("/contract/encode", body).await?;
        response["data"]
            .as_str()
            .map(|s| s.to_string())
            .ok_or_else(|| ContractError::Response("Missing data in response".to_string()))
    }

    /// Decode a function result
    pub async fn decode_result(&self, options: DecodeResultOptions) -> Result<serde_json::Value> {
        let body = serde_json::json!({
            "data": options.data,
            "method": options.method,
            "abi": options.abi,
        });

        let response: serde_json::Value = self.post("/contract/decode", body).await?;
        Ok(response["result"].clone())
    }

    /// Get the function selector for a method
    pub async fn get_selector(&self, signature: &str) -> Result<String> {
        let response: serde_json::Value = self
            .get(&format!("/contract/selector?signature={}", urlencoding::encode(signature)))
            .await?;
        response["selector"]
            .as_str()
            .map(|s| s.to_string())
            .ok_or_else(|| ContractError::Response("Missing selector in response".to_string()))
    }

    // ==================== Gas Estimation ====================

    /// Estimate gas for a contract interaction
    pub async fn estimate_gas(&self, options: EstimateGasOptions) -> Result<GasEstimation> {
        let mut body = serde_json::json!({
            "contract": options.contract,
            "method": options.method,
            "args": options.args,
            "abi": options.abi,
        });

        if let Some(value) = &options.value {
            body["value"] = serde_json::json!(value);
        }

        self.post("/contract/estimate-gas", body).await
    }

    // ==================== Contract Information ====================

    /// Get bytecode deployed at an address
    pub async fn get_bytecode(&self, address: &str) -> Result<BytecodeInfo> {
        self.get(&format!("/contract/{}/bytecode", address)).await
    }

    /// Verify contract source code
    pub async fn verify(&self, options: VerifyContractOptions) -> Result<VerificationResult> {
        let mut body = serde_json::json!({
            "address": options.address,
            "source_code": options.source_code,
            "compiler_version": options.compiler_version,
        });

        if let Some(ctor_args) = &options.constructor_args {
            body["constructor_args"] = serde_json::json!(ctor_args);
        }
        if let Some(optimization) = options.optimization {
            body["optimization"] = serde_json::json!(optimization);
        }
        if let Some(runs) = options.optimization_runs {
            body["optimization_runs"] = serde_json::json!(runs);
        }
        if let Some(license) = &options.license {
            body["license"] = serde_json::json!(license);
        }

        self.post("/contract/verify", body).await
    }

    /// Get verification status for a contract
    pub async fn get_verification_status(&self, address: &str) -> Result<VerificationResult> {
        self.get(&format!("/contract/{}/verification", address)).await
    }

    // ==================== Multicall ====================

    /// Execute multiple calls in a single request
    pub async fn multicall(&self, requests: &[MulticallRequest]) -> Result<Vec<MulticallResult>> {
        let body = serde_json::json!({
            "calls": requests,
        });

        self.post("/contract/multicall", body).await
    }

    // ==================== Storage ====================

    /// Read a storage slot from a contract
    pub async fn read_storage(&self, options: ReadStorageOptions) -> Result<String> {
        let mut params = format!("contract={}&slot={}", options.contract, options.slot);
        if let Some(block) = options.block_number {
            params.push_str(&format!("&block={}", block));
        }

        let response: serde_json::Value = self.get(&format!("/contract/storage?{}", params)).await?;
        response["value"]
            .as_str()
            .map(|s| s.to_string())
            .ok_or_else(|| ContractError::Response("Missing value in response".to_string()))
    }

    // ==================== Lifecycle ====================

    /// Check if the service is healthy
    pub async fn health_check(&self) -> bool {
        if self.closed.load(Ordering::SeqCst) {
            return false;
        }

        match self.get::<serde_json::Value>("/health").await {
            Ok(response) => response.get("status").and_then(|s| s.as_str()) == Some("healthy"),
            Err(_) => false,
        }
    }

    /// Close the client
    pub fn close(&self) {
        self.closed.store(true, Ordering::SeqCst);
    }

    /// Check if the client is closed
    pub fn is_closed(&self) -> bool {
        self.closed.load(Ordering::SeqCst)
    }
}
