//! Privacy client implementation

use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;

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

/// Synor Privacy Client
pub struct PrivacyClient {
    config: PrivacyConfig,
    http_client: reqwest::Client,
    closed: Arc<AtomicBool>,
}

impl PrivacyClient {
    /// Create a new privacy client
    pub fn new(config: PrivacyConfig) -> Self {
        let mut headers = HeaderMap::new();
        headers.insert(
            AUTHORIZATION,
            HeaderValue::from_str(&format!("Bearer {}", config.api_key)).unwrap(),
        );
        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
        headers.insert("X-SDK-Version", HeaderValue::from_static("rust/0.1.0"));

        let http_client = reqwest::Client::builder()
            .default_headers(headers)
            .timeout(Duration::from_secs(config.timeout_secs))
            .build()
            .expect("Failed to create HTTP client");

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

    // ==================== Confidential Transactions ====================

    /// Create a confidential transaction
    pub async fn create_confidential_transaction(
        &self,
        opts: CreateConfidentialTxOptions,
    ) -> Result<ConfidentialTransaction> {
        #[derive(Serialize)]
        struct Request {
            inputs: Vec<ConfidentialUtxo>,
            outputs: Vec<ConfidentialOutput>,
            #[serde(skip_serializing_if = "Option::is_none")]
            fee: Option<String>,
            #[serde(rename = "lockHeight", skip_serializing_if = "Option::is_none")]
            lock_height: Option<u64>,
        }

        #[derive(Deserialize)]
        struct Response {
            transaction: ConfidentialTransaction,
        }

        let resp: Response = self.request(
            "POST",
            "/transactions/confidential",
            Some(Request {
                inputs: opts.inputs,
                outputs: opts.outputs,
                fee: opts.fee,
                lock_height: opts.lock_height,
            }),
        ).await?;
        Ok(resp.transaction)
    }

    /// Verify a confidential transaction
    pub async fn verify_confidential_transaction(
        &self,
        tx: &ConfidentialTransaction,
    ) -> Result<VerifyConfidentialTxResult> {
        #[derive(Serialize)]
        struct Request {
            transaction: String,
        }
        self.request(
            "POST",
            "/transactions/confidential/verify",
            Some(Request { transaction: tx.raw.clone() }),
        ).await
    }

    /// Decode a confidential output
    pub async fn decode_confidential_output(
        &self,
        commitment: &str,
        blinding: &str,
    ) -> Result<String> {
        #[derive(Serialize)]
        struct Request {
            commitment: String,
            blinding: String,
        }
        #[derive(Deserialize)]
        struct Response {
            amount: String,
        }
        let resp: Response = self.request(
            "POST",
            "/transactions/confidential/decode",
            Some(Request {
                commitment: commitment.to_string(),
                blinding: blinding.to_string(),
            }),
        ).await?;
        Ok(resp.amount)
    }

    // ==================== Ring Signatures ====================

    /// Create a ring signature
    pub async fn create_ring_signature(
        &self,
        opts: CreateRingSignatureOptions,
    ) -> Result<RingSignature> {
        #[derive(Serialize)]
        struct Request {
            message: String,
            ring: Vec<String>,
            #[serde(rename = "privateKey")]
            private_key: String,
            #[serde(rename = "signerIndex", skip_serializing_if = "Option::is_none")]
            signer_index: Option<u32>,
        }
        #[derive(Deserialize)]
        struct Response {
            signature: RingSignature,
        }
        let resp: Response = self.request(
            "POST",
            "/ring-signatures/create",
            Some(Request {
                message: opts.message,
                ring: opts.ring,
                private_key: opts.private_key,
                signer_index: opts.signer_index,
            }),
        ).await?;
        Ok(resp.signature)
    }

    /// Verify a ring signature
    pub async fn verify_ring_signature(
        &self,
        signature: &RingSignature,
        message: &str,
    ) -> Result<VerifyRingSignatureResult> {
        #[derive(Serialize)]
        struct Request<'a> {
            signature: &'a RingSignature,
            message: String,
        }
        self.request(
            "POST",
            "/ring-signatures/verify",
            Some(Request {
                signature,
                message: message.to_string(),
            }),
        ).await
    }

    /// Check if a key image has been used
    pub async fn is_key_image_used(&self, key_image: &str) -> Result<bool> {
        #[derive(Deserialize)]
        struct Response {
            used: bool,
        }
        let resp: Response = self.request(
            "GET",
            &format!("/ring-signatures/key-images/{}", urlencoding::encode(key_image)),
            Option::<()>::None,
        ).await?;
        Ok(resp.used)
    }

    /// Generate a random ring
    pub async fn generate_random_ring(
        &self,
        size: u32,
        exclude: Option<Vec<String>>,
    ) -> Result<Vec<String>> {
        #[derive(Serialize)]
        struct Request {
            size: u32,
            #[serde(skip_serializing_if = "Option::is_none")]
            exclude: Option<Vec<String>>,
        }
        #[derive(Deserialize)]
        struct Response {
            ring: Vec<String>,
        }
        let resp: Response = self.request(
            "POST",
            "/ring-signatures/random-ring",
            Some(Request { size, exclude }),
        ).await?;
        Ok(resp.ring)
    }

    // ==================== Stealth Addresses ====================

    /// Generate a stealth keypair
    pub async fn generate_stealth_keypair(&self) -> Result<StealthKeypair> {
        #[derive(Deserialize)]
        struct Response {
            keypair: StealthKeypair,
        }
        let resp: Response = self.request("POST", "/stealth/generate", Option::<()>::None).await?;
        Ok(resp.keypair)
    }

    /// Create a one-time address
    pub async fn create_one_time_address(
        &self,
        stealth_address: &StealthAddress,
    ) -> Result<OneTimeAddress> {
        #[derive(Serialize)]
        struct Request {
            #[serde(rename = "scanPublicKey")]
            scan_public_key: String,
            #[serde(rename = "spendPublicKey")]
            spend_public_key: String,
        }
        #[derive(Deserialize)]
        struct Response {
            #[serde(rename = "oneTimeAddress")]
            one_time_address: OneTimeAddress,
        }
        let resp: Response = self.request(
            "POST",
            "/stealth/one-time-address",
            Some(Request {
                scan_public_key: stealth_address.scan_public_key.clone(),
                spend_public_key: stealth_address.spend_public_key.clone(),
            }),
        ).await?;
        Ok(resp.one_time_address)
    }

    /// Derive shared secret
    pub async fn derive_shared_secret(
        &self,
        stealth_address: &StealthAddress,
        private_key: &str,
        ephemeral_public_key: &str,
    ) -> Result<SharedSecret> {
        #[derive(Serialize)]
        struct Request {
            #[serde(rename = "scanPublicKey")]
            scan_public_key: String,
            #[serde(rename = "spendPublicKey")]
            spend_public_key: String,
            #[serde(rename = "privateKey")]
            private_key: String,
            #[serde(rename = "ephemeralPublicKey")]
            ephemeral_public_key: String,
        }
        self.request(
            "POST",
            "/stealth/derive-secret",
            Some(Request {
                scan_public_key: stealth_address.scan_public_key.clone(),
                spend_public_key: stealth_address.spend_public_key.clone(),
                private_key: private_key.to_string(),
                ephemeral_public_key: ephemeral_public_key.to_string(),
            }),
        ).await
    }

    /// Scan for payments
    pub async fn scan_for_payments(
        &self,
        scan_private_key: &str,
        spend_public_key: &str,
        transactions: Vec<String>,
    ) -> Result<Vec<OneTimeAddress>> {
        #[derive(Serialize)]
        struct Request {
            #[serde(rename = "scanPrivateKey")]
            scan_private_key: String,
            #[serde(rename = "spendPublicKey")]
            spend_public_key: String,
            transactions: Vec<String>,
        }
        #[derive(Deserialize)]
        struct Response {
            payments: Vec<OneTimeAddress>,
        }
        let resp: Response = self.request(
            "POST",
            "/stealth/scan",
            Some(Request {
                scan_private_key: scan_private_key.to_string(),
                spend_public_key: spend_public_key.to_string(),
                transactions,
            }),
        ).await?;
        Ok(resp.payments)
    }

    // ==================== Commitments ====================

    /// Create a commitment
    pub async fn create_commitment(
        &self,
        value: &str,
        blinding: Option<&str>,
    ) -> Result<CommitmentWithBlinding> {
        #[derive(Serialize)]
        struct Request {
            value: String,
            #[serde(skip_serializing_if = "Option::is_none")]
            blinding: Option<String>,
        }
        self.request(
            "POST",
            "/commitments/create",
            Some(Request {
                value: value.to_string(),
                blinding: blinding.map(|s| s.to_string()),
            }),
        ).await
    }

    /// Open (verify) a commitment
    pub async fn open_commitment(
        &self,
        commitment: &str,
        value: &str,
        blinding: &str,
    ) -> Result<bool> {
        #[derive(Serialize)]
        struct Request {
            commitment: String,
            value: String,
            blinding: String,
        }
        #[derive(Deserialize)]
        struct Response {
            valid: bool,
        }
        let resp: Response = self.request(
            "POST",
            "/commitments/open",
            Some(Request {
                commitment: commitment.to_string(),
                value: value.to_string(),
                blinding: blinding.to_string(),
            }),
        ).await?;
        Ok(resp.valid)
    }

    /// Add two commitments
    pub async fn add_commitments(
        &self,
        commitment1: &str,
        commitment2: &str,
    ) -> Result<String> {
        #[derive(Serialize)]
        struct Request {
            commitment1: String,
            commitment2: String,
        }
        #[derive(Deserialize)]
        struct Response {
            commitment: String,
        }
        let resp: Response = self.request(
            "POST",
            "/commitments/add",
            Some(Request {
                commitment1: commitment1.to_string(),
                commitment2: commitment2.to_string(),
            }),
        ).await?;
        Ok(resp.commitment)
    }

    /// Subtract two commitments
    pub async fn subtract_commitments(
        &self,
        commitment1: &str,
        commitment2: &str,
    ) -> Result<String> {
        #[derive(Serialize)]
        struct Request {
            commitment1: String,
            commitment2: String,
        }
        #[derive(Deserialize)]
        struct Response {
            commitment: String,
        }
        let resp: Response = self.request(
            "POST",
            "/commitments/subtract",
            Some(Request {
                commitment1: commitment1.to_string(),
                commitment2: commitment2.to_string(),
            }),
        ).await?;
        Ok(resp.commitment)
    }

    /// Compute blinding sum
    pub async fn compute_blinding_sum(
        &self,
        positive: Vec<String>,
        negative: Vec<String>,
    ) -> Result<String> {
        #[derive(Serialize)]
        struct Request {
            positive: Vec<String>,
            negative: Vec<String>,
        }
        #[derive(Deserialize)]
        struct Response {
            sum: String,
        }
        let resp: Response = self.request(
            "POST",
            "/commitments/blinding-sum",
            Some(Request { positive, negative }),
        ).await?;
        Ok(resp.sum)
    }

    /// Generate a random blinding factor
    pub async fn generate_blinding(&self) -> Result<String> {
        #[derive(Deserialize)]
        struct Response {
            blinding: String,
        }
        let resp: Response = self.request("POST", "/commitments/random-blinding", Option::<()>::None).await?;
        Ok(resp.blinding)
    }

    // ==================== Range Proofs ====================

    /// Create a range proof
    pub async fn create_range_proof(&self, opts: CreateRangeProofOptions) -> Result<RangeProof> {
        #[derive(Serialize)]
        struct Request {
            value: String,
            blinding: String,
            #[serde(skip_serializing_if = "Option::is_none")]
            message: Option<String>,
            #[serde(rename = "bitLength")]
            bit_length: u32,
        }
        #[derive(Deserialize)]
        struct Response {
            proof: RangeProof,
        }
        let resp: Response = self.request(
            "POST",
            "/range-proofs/create",
            Some(Request {
                value: opts.value,
                blinding: opts.blinding,
                message: opts.message,
                bit_length: opts.bit_length.unwrap_or(64),
            }),
        ).await?;
        Ok(resp.proof)
    }

    /// Verify a range proof
    pub async fn verify_range_proof(
        &self,
        commitment: &str,
        proof: &str,
    ) -> Result<VerifyRangeProofResult> {
        #[derive(Serialize)]
        struct Request {
            commitment: String,
            proof: String,
        }
        self.request(
            "POST",
            "/range-proofs/verify",
            Some(Request {
                commitment: commitment.to_string(),
                proof: proof.to_string(),
            }),
        ).await
    }

    /// Create aggregated range proof
    pub async fn create_aggregated_range_proof(
        &self,
        outputs: Vec<(String, String)>,
    ) -> Result<String> {
        #[derive(Serialize)]
        struct Output {
            value: String,
            blinding: String,
        }
        #[derive(Serialize)]
        struct Request {
            outputs: Vec<Output>,
        }
        #[derive(Deserialize)]
        struct Response {
            proof: String,
        }
        let resp: Response = self.request(
            "POST",
            "/range-proofs/aggregate",
            Some(Request {
                outputs: outputs.into_iter().map(|(v, b)| Output { value: v, blinding: b }).collect(),
            }),
        ).await?;
        Ok(resp.proof)
    }

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

    /// Health check
    pub async fn health_check(&self) -> bool {
        #[derive(Deserialize)]
        struct Response {
            status: String,
        }
        match self.request::<_, Response>("GET", "/health", Option::<()>::None).await {
            Ok(resp) => resp.status == "healthy",
            Err(_) => false,
        }
    }

    /// 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)
    }

    // ==================== Private ====================

    async fn request<B, R>(&self, method: &str, path: &str, body: Option<B>) -> Result<R>
    where
        B: Serialize,
        R: DeserializeOwned,
    {
        if self.closed.load(Ordering::SeqCst) {
            return Err(PrivacyError::Closed);
        }

        let mut last_error = None;
        for attempt in 0..self.config.retries {
            match self.do_request(method, path, &body).await {
                Ok(result) => return Ok(result),
                Err(e) => {
                    last_error = Some(e);
                    if attempt < self.config.retries - 1 {
                        tokio::time::sleep(Duration::from_secs(1 << attempt)).await;
                    }
                }
            }
        }
        Err(last_error.unwrap_or(PrivacyError::Request("Unknown error".to_string())))
    }

    async fn do_request<B, R>(&self, method: &str, path: &str, body: &Option<B>) -> Result<R>
    where
        B: Serialize,
        R: DeserializeOwned,
    {
        let url = format!("{}{}", self.config.endpoint, path);

        let mut req = match method {
            "GET" => self.http_client.get(&url),
            "POST" => self.http_client.post(&url),
            "PUT" => self.http_client.put(&url),
            "DELETE" => self.http_client.delete(&url),
            _ => return Err(PrivacyError::Request(format!("Unknown method: {}", method))),
        };

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

        let response = req.send().await?;
        let status = response.status();

        if status.is_client_error() || status.is_server_error() {
            #[derive(Deserialize)]
            struct ErrorResponse {
                message: Option<String>,
                error: Option<String>,
                code: Option<String>,
            }
            let error_body: ErrorResponse = response.json().await.unwrap_or(ErrorResponse {
                message: None,
                error: None,
                code: None,
            });
            let message = error_body.message
                .or(error_body.error)
                .unwrap_or_else(|| format!("HTTP {}", status));
            return Err(PrivacyError::Api {
                message,
                code: error_body.code,
                status_code: Some(status.as_u16()),
            });
        }

        response.json().await.map_err(|e| PrivacyError::Response(e.to_string()))
    }
}
