//! Synor Compiler SDK Client
//!
//! Smart contract compilation, optimization, and ABI generation.

use base64::Engine;
use reqwest::Client;
use serde::de::DeserializeOwned;
use serde_json::json;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;

use super::types::*;

/// Synor Compiler SDK Client.
///
/// Smart contract compilation, optimization, and ABI generation.
///
/// # Example
///
/// ```ignore
/// use synor_sdk::compiler::{SynorCompiler, CompilerConfig, OptimizationLevel};
///
/// let config = CompilerConfig::new("your-api-key");
/// let compiler = SynorCompiler::new(config)?;
///
/// let result = compiler.compile(&wasm_bytes, None).await?;
/// println!("Optimized size: {} bytes", result.optimized_size);
/// ```
pub struct SynorCompiler {
    config: CompilerConfig,
    client: Client,
    closed: Arc<AtomicBool>,
}

impl SynorCompiler {
    /// Create a new compiler client.
    pub fn new(config: CompilerConfig) -> Result<Self, CompilerError> {
        let client = Client::builder()
            .timeout(Duration::from_millis(config.timeout_ms))
            .build()
            .map_err(|e| CompilerError {
                message: e.to_string(),
                code: Some("HTTP_CLIENT_ERROR".to_string()),
                http_status: None,
            })?;

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

    /// Get the default optimization level.
    pub fn default_optimization_level(&self) -> OptimizationLevel {
        self.config.optimization_level
    }

    /// Check service health.
    pub async fn health_check(&self) -> bool {
        match self.get::<serde_json::Value>("/health").await {
            Ok(result) => result.get("status").and_then(|v| v.as_str()) == Some("healthy"),
            Err(_) => false,
        }
    }

    /// Get service info.
    pub async fn get_info(&self) -> Result<serde_json::Value, CompilerError> {
        self.get("/info").await
    }

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

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

    /// Compile WASM bytecode.
    pub async fn compile(
        &self,
        wasm: &[u8],
        opts: Option<CompilationRequest>,
    ) -> Result<CompilationResult, CompilerError> {
        let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm);

        let body = if let Some(opts) = opts {
            json!({
                "wasm": wasm_base64,
                "optimization_level": opts.optimization_level.unwrap_or(self.config.optimization_level),
                "strip_options": opts.strip_options.or(self.config.strip_options.clone()),
                "use_wasm_opt": opts.use_wasm_opt.unwrap_or(self.config.use_wasm_opt),
                "validate": opts.validate.unwrap_or(self.config.validate),
                "extract_metadata": opts.extract_metadata.unwrap_or(self.config.extract_metadata),
                "generate_abi": opts.generate_abi.unwrap_or(self.config.generate_abi),
            })
        } else {
            json!({
                "wasm": wasm_base64,
                "optimization_level": self.config.optimization_level,
                "use_wasm_opt": self.config.use_wasm_opt,
                "validate": self.config.validate,
                "extract_metadata": self.config.extract_metadata,
                "generate_abi": self.config.generate_abi,
            })
        };

        self.post("/compile", body).await
    }

    /// Get sub-client for contracts.
    pub fn contracts(&self) -> ContractsClient<'_> {
        ContractsClient { compiler: self }
    }

    /// Get sub-client for ABI operations.
    pub fn abi(&self) -> AbiClient<'_> {
        AbiClient { compiler: self }
    }

    /// Get sub-client for analysis.
    pub fn analysis(&self) -> AnalysisClient<'_> {
        AnalysisClient { compiler: self }
    }

    /// Get sub-client for validation.
    pub fn validation(&self) -> ValidationClient<'_> {
        ValidationClient { compiler: self }
    }

    async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, CompilerError> {
        self.check_closed()?;

        let response = self
            .client
            .get(format!("{}{}", self.config.endpoint, path))
            .header("Authorization", format!("Bearer {}", self.config.api_key))
            .header("Content-Type", "application/json")
            .header("X-SDK-Version", "rust/0.1.0")
            .send()
            .await
            .map_err(|e| CompilerError {
                message: e.to_string(),
                code: Some("NETWORK_ERROR".to_string()),
                http_status: None,
            })?;

        self.handle_response(response).await
    }

    async fn post<T: DeserializeOwned>(
        &self,
        path: &str,
        body: serde_json::Value,
    ) -> Result<T, CompilerError> {
        self.check_closed()?;

        let response = self
            .client
            .post(format!("{}{}", self.config.endpoint, path))
            .header("Authorization", format!("Bearer {}", self.config.api_key))
            .header("Content-Type", "application/json")
            .header("X-SDK-Version", "rust/0.1.0")
            .json(&body)
            .send()
            .await
            .map_err(|e| CompilerError {
                message: e.to_string(),
                code: Some("NETWORK_ERROR".to_string()),
                http_status: None,
            })?;

        self.handle_response(response).await
    }

    async fn handle_response<T: DeserializeOwned>(
        &self,
        response: reqwest::Response,
    ) -> Result<T, CompilerError> {
        let status = response.status();

        if !status.is_success() {
            let text = response.text().await.unwrap_or_default();
            let error: serde_json::Value = serde_json::from_str(&text).unwrap_or_default();

            return Err(CompilerError {
                message: error
                    .get("message")
                    .and_then(|v| v.as_str())
                    .unwrap_or(&format!("HTTP {}", status.as_u16()))
                    .to_string(),
                code: error
                    .get("code")
                    .and_then(|v| v.as_str())
                    .map(|s| s.to_string()),
                http_status: Some(status.as_u16()),
            });
        }

        response.json().await.map_err(|e| CompilerError {
            message: e.to_string(),
            code: Some("PARSE_ERROR".to_string()),
            http_status: None,
        })
    }

    fn check_closed(&self) -> Result<(), CompilerError> {
        if self.closed.load(Ordering::SeqCst) {
            return Err(CompilerError {
                message: "Client has been closed".to_string(),
                code: Some("CLIENT_CLOSED".to_string()),
                http_status: None,
            });
        }
        Ok(())
    }
}

/// Contracts sub-client.
pub struct ContractsClient<'a> {
    compiler: &'a SynorCompiler,
}

impl<'a> ContractsClient<'a> {
    /// Compile WASM bytecode with default settings.
    pub async fn compile(
        &self,
        wasm: &[u8],
        opts: Option<CompilationRequest>,
    ) -> Result<CompilationResult, CompilerError> {
        self.compiler.compile(wasm, opts).await
    }

    /// Compile with development settings.
    pub async fn compile_dev(&self, wasm: &[u8]) -> Result<CompilationResult, CompilerError> {
        self.compiler
            .compile(
                wasm,
                Some(CompilationRequest {
                    wasm: String::new(),
                    optimization_level: Some(OptimizationLevel::None),
                    strip_options: None,
                    use_wasm_opt: Some(false),
                    validate: Some(true),
                    extract_metadata: None,
                    generate_abi: None,
                }),
            )
            .await
    }

    /// Compile with production settings.
    pub async fn compile_production(
        &self,
        wasm: &[u8],
    ) -> Result<CompilationResult, CompilerError> {
        self.compiler
            .compile(
                wasm,
                Some(CompilationRequest {
                    wasm: String::new(),
                    optimization_level: Some(OptimizationLevel::Aggressive),
                    strip_options: None,
                    use_wasm_opt: Some(true),
                    validate: Some(true),
                    extract_metadata: Some(true),
                    generate_abi: Some(true),
                }),
            )
            .await
    }

    /// Get a compiled contract by ID.
    pub async fn get(&self, contract_id: &str) -> Result<CompilationResult, CompilerError> {
        self.compiler
            .get(&format!("/contracts/{}", contract_id))
            .await
    }

    /// List compiled contracts.
    pub async fn list(
        &self,
        limit: Option<u32>,
        offset: Option<u32>,
    ) -> Result<Vec<CompilationResult>, CompilerError> {
        let mut path = "/contracts".to_string();
        let mut params = Vec::new();
        if let Some(l) = limit {
            params.push(format!("limit={}", l));
        }
        if let Some(o) = offset {
            params.push(format!("offset={}", o));
        }
        if !params.is_empty() {
            path.push('?');
            path.push_str(&params.join("&"));
        }

        #[derive(serde::Deserialize)]
        struct Response {
            contracts: Vec<CompilationResult>,
        }

        let result: Response = self.compiler.get(&path).await?;
        Ok(result.contracts)
    }

    /// Get optimized bytecode for a contract.
    pub async fn get_code(&self, contract_id: &str) -> Result<Vec<u8>, CompilerError> {
        #[derive(serde::Deserialize)]
        struct Response {
            code: String,
        }

        let result: Response = self
            .compiler
            .get(&format!("/contracts/{}/code", contract_id))
            .await?;

        base64::engine::general_purpose::STANDARD
            .decode(&result.code)
            .map_err(|e| CompilerError {
                message: e.to_string(),
                code: Some("DECODE_ERROR".to_string()),
                http_status: None,
            })
    }
}

/// ABI sub-client.
pub struct AbiClient<'a> {
    compiler: &'a SynorCompiler,
}

impl<'a> AbiClient<'a> {
    /// Extract ABI from WASM bytecode.
    pub async fn extract(&self, wasm: &[u8]) -> Result<ContractAbi, CompilerError> {
        let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm);
        self.compiler
            .post("/abi/extract", json!({ "wasm": wasm_base64 }))
            .await
    }

    /// Get ABI for a compiled contract.
    pub async fn get(&self, contract_id: &str) -> Result<ContractAbi, CompilerError> {
        self.compiler
            .get(&format!("/contracts/{}/abi", contract_id))
            .await
    }

    /// Encode function call.
    pub async fn encode_call(
        &self,
        function_abi: &FunctionAbi,
        args: Vec<serde_json::Value>,
    ) -> Result<String, CompilerError> {
        #[derive(serde::Deserialize)]
        struct Response {
            data: String,
        }

        let result: Response = self
            .compiler
            .post(
                "/abi/encode",
                json!({
                    "function": function_abi,
                    "args": args,
                }),
            )
            .await?;

        Ok(result.data)
    }

    /// Decode function result.
    pub async fn decode_result(
        &self,
        function_abi: &FunctionAbi,
        data: &str,
    ) -> Result<Vec<serde_json::Value>, CompilerError> {
        #[derive(serde::Deserialize)]
        struct Response {
            values: Vec<serde_json::Value>,
        }

        let result: Response = self
            .compiler
            .post(
                "/abi/decode",
                json!({
                    "function": function_abi,
                    "data": data,
                }),
            )
            .await?;

        Ok(result.values)
    }
}

/// Analysis sub-client.
pub struct AnalysisClient<'a> {
    compiler: &'a SynorCompiler,
}

impl<'a> AnalysisClient<'a> {
    /// Analyze WASM bytecode.
    pub async fn analyze(&self, wasm: &[u8]) -> Result<ContractAnalysis, CompilerError> {
        let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm);
        self.compiler
            .post("/analysis", json!({ "wasm": wasm_base64 }))
            .await
    }

    /// Get analysis for a compiled contract.
    pub async fn get(&self, contract_id: &str) -> Result<ContractAnalysis, CompilerError> {
        self.compiler
            .get(&format!("/contracts/{}/analysis", contract_id))
            .await
    }

    /// Extract metadata from WASM bytecode.
    pub async fn extract_metadata(&self, wasm: &[u8]) -> Result<ContractMetadata, CompilerError> {
        let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm);
        self.compiler
            .post("/analysis/metadata", json!({ "wasm": wasm_base64 }))
            .await
    }

    /// Extract source map from WASM bytecode.
    pub async fn extract_source_map(&self, wasm: &[u8]) -> Result<Option<SourceMap>, CompilerError> {
        let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm);

        #[derive(serde::Deserialize)]
        struct Response {
            source_map: Option<SourceMap>,
        }

        let result: Response = self
            .compiler
            .post("/analysis/source-map", json!({ "wasm": wasm_base64 }))
            .await?;

        Ok(result.source_map)
    }

    /// Estimate gas for contract deployment.
    pub async fn estimate_deploy_gas(&self, wasm: &[u8]) -> Result<u64, CompilerError> {
        let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm);

        #[derive(serde::Deserialize)]
        struct Response {
            gas: u64,
        }

        let result: Response = self
            .compiler
            .post("/analysis/estimate-gas", json!({ "wasm": wasm_base64 }))
            .await?;

        Ok(result.gas)
    }

    /// Get security analysis.
    pub async fn security_scan(&self, wasm: &[u8]) -> Result<SecurityAnalysis, CompilerError> {
        let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm);

        #[derive(serde::Deserialize)]
        struct Response {
            security: SecurityAnalysis,
        }

        let result: Response = self
            .compiler
            .post("/analysis/security", json!({ "wasm": wasm_base64 }))
            .await?;

        Ok(result.security)
    }
}

/// Validation sub-client.
pub struct ValidationClient<'a> {
    compiler: &'a SynorCompiler,
}

impl<'a> ValidationClient<'a> {
    /// Validate WASM bytecode against VM requirements.
    pub async fn validate(&self, wasm: &[u8]) -> Result<ValidationResult, CompilerError> {
        let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm);
        self.compiler
            .post("/validate", json!({ "wasm": wasm_base64 }))
            .await
    }

    /// Check if WASM is valid.
    pub async fn is_valid(&self, wasm: &[u8]) -> Result<bool, CompilerError> {
        let result = self.validate(wasm).await?;
        Ok(result.valid)
    }

    /// Get validation errors.
    pub async fn get_errors(&self, wasm: &[u8]) -> Result<Vec<String>, CompilerError> {
        let result = self.validate(wasm).await?;
        Ok(result.errors.into_iter().map(|e| e.message).collect())
    }

    /// Validate contract exports.
    pub async fn validate_exports(
        &self,
        wasm: &[u8],
        required_exports: Vec<String>,
    ) -> Result<bool, CompilerError> {
        let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm);

        #[derive(serde::Deserialize)]
        struct Response {
            valid: bool,
        }

        let result: Response = self
            .compiler
            .post(
                "/validate/exports",
                json!({
                    "wasm": wasm_base64,
                    "required_exports": required_exports,
                }),
            )
            .await?;

        Ok(result.valid)
    }

    /// Validate memory limits.
    pub async fn validate_memory(&self, wasm: &[u8], max_pages: u32) -> Result<bool, CompilerError> {
        let wasm_base64 = base64::engine::general_purpose::STANDARD.encode(wasm);

        #[derive(serde::Deserialize)]
        struct Response {
            valid: bool,
        }

        let result: Response = self
            .compiler
            .post(
                "/validate/memory",
                json!({
                    "wasm": wasm_base64,
                    "max_pages": max_pages,
                }),
            )
            .await?;

        Ok(result.valid)
    }
}
