//! Bridge client implementation

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

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

/// Synor Bridge Client
pub struct BridgeClient {
    config: BridgeConfig,
    http_client: reqwest::Client,
    closed: Arc<AtomicBool>,
}

impl BridgeClient {
    /// Create a new bridge client
    pub fn new(config: BridgeConfig) -> 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)),
        }
    }

    // ==================== Chain Operations ====================

    /// Get all supported chains
    pub async fn get_supported_chains(&self) -> Result<Vec<Chain>> {
        #[derive(Deserialize)]
        struct Response {
            chains: Vec<Chain>,
        }
        let resp: Response = self.request("GET", "/chains", Option::<()>::None).await?;
        Ok(resp.chains)
    }

    /// Get chain by ID
    pub async fn get_chain(&self, chain_id: ChainId) -> Result<Chain> {
        self.request("GET", &format!("/chains/{:?}", chain_id).to_lowercase(), Option::<()>::None).await
    }

    /// Check if chain is supported
    pub async fn is_chain_supported(&self, chain_id: ChainId) -> bool {
        match self.get_chain(chain_id).await {
            Ok(chain) => chain.supported,
            Err(_) => false,
        }
    }

    // ==================== Asset Operations ====================

    /// Get supported assets for a chain
    pub async fn get_supported_assets(&self, chain_id: ChainId) -> Result<Vec<Asset>> {
        #[derive(Deserialize)]
        struct Response {
            assets: Vec<Asset>,
        }
        let resp: Response = self.request(
            "GET",
            &format!("/chains/{:?}/assets", chain_id).to_lowercase(),
            Option::<()>::None,
        ).await?;
        Ok(resp.assets)
    }

    /// Get asset by ID
    pub async fn get_asset(&self, asset_id: &str) -> Result<Asset> {
        self.request("GET", &format!("/assets/{}", urlencoding::encode(asset_id)), Option::<()>::None).await
    }

    /// Get wrapped asset mapping
    pub async fn get_wrapped_asset(&self, original_asset_id: &str, target_chain: ChainId) -> Result<WrappedAsset> {
        self.request(
            "GET",
            &format!("/assets/{}/wrapped/{:?}", urlencoding::encode(original_asset_id), target_chain).to_lowercase(),
            Option::<()>::None,
        ).await
    }

    /// Get all wrapped assets for a chain
    pub async fn get_wrapped_assets(&self, chain_id: ChainId) -> Result<Vec<WrappedAsset>> {
        #[derive(Deserialize)]
        struct Response {
            assets: Vec<WrappedAsset>,
        }
        let resp: Response = self.request(
            "GET",
            &format!("/chains/{:?}/wrapped", chain_id).to_lowercase(),
            Option::<()>::None,
        ).await?;
        Ok(resp.assets)
    }

    // ==================== Fee & Rate Operations ====================

    /// Estimate bridge fee
    pub async fn estimate_fee(
        &self,
        asset: &str,
        amount: &str,
        source_chain: ChainId,
        target_chain: ChainId,
    ) -> Result<FeeEstimate> {
        #[derive(Serialize)]
        struct Request {
            asset: String,
            amount: String,
            #[serde(rename = "sourceChain")]
            source_chain: ChainId,
            #[serde(rename = "targetChain")]
            target_chain: ChainId,
        }
        self.request("POST", "/fees/estimate", Some(Request {
            asset: asset.to_string(),
            amount: amount.to_string(),
            source_chain,
            target_chain,
        })).await
    }

    /// Get exchange rate between assets
    pub async fn get_exchange_rate(&self, from_asset: &str, to_asset: &str) -> Result<ExchangeRate> {
        self.request(
            "GET",
            &format!("/rates/{}/{}", urlencoding::encode(from_asset), urlencoding::encode(to_asset)),
            Option::<()>::None,
        ).await
    }

    // ==================== Lock-Mint Flow ====================

    /// Lock assets on source chain
    pub async fn lock(
        &self,
        asset: &str,
        amount: &str,
        target_chain: ChainId,
        options: Option<LockOptions>,
    ) -> Result<LockReceipt> {
        #[derive(Serialize)]
        struct Request {
            asset: String,
            amount: String,
            #[serde(rename = "targetChain")]
            target_chain: ChainId,
            #[serde(flatten)]
            options: Option<LockOptions>,
        }
        self.request("POST", "/transfers/lock", Some(Request {
            asset: asset.to_string(),
            amount: amount.to_string(),
            target_chain,
            options,
        })).await
    }

    /// Get lock proof for minting
    pub async fn get_lock_proof(&self, lock_receipt_id: &str) -> Result<LockProof> {
        self.request(
            "GET",
            &format!("/transfers/lock/{}/proof", urlencoding::encode(lock_receipt_id)),
            Option::<()>::None,
        ).await
    }

    /// Wait for lock proof to be ready
    pub async fn wait_for_lock_proof(
        &self,
        lock_receipt_id: &str,
        poll_interval: Option<Duration>,
        max_wait: Option<Duration>,
    ) -> Result<LockProof> {
        let poll_interval = poll_interval.unwrap_or(Duration::from_secs(5));
        let max_wait = max_wait.unwrap_or(Duration::from_secs(600));
        let deadline = std::time::Instant::now() + max_wait;

        loop {
            if std::time::Instant::now() >= deadline {
                return Err(BridgeError::new("Timeout waiting for lock proof")
                    .with_code("CONFIRMATIONS_PENDING"));
            }

            match self.get_lock_proof(lock_receipt_id).await {
                Ok(proof) => return Ok(proof),
                Err(e) if e.is_confirmations_pending() => {
                    tokio::time::sleep(poll_interval).await;
                }
                Err(e) => return Err(e),
            }
        }
    }

    /// Mint wrapped tokens on target chain
    pub async fn mint(
        &self,
        proof: &LockProof,
        target_address: &str,
        options: Option<MintOptions>,
    ) -> Result<SignedTransaction> {
        #[derive(Serialize)]
        struct Request<'a> {
            proof: &'a LockProof,
            #[serde(rename = "targetAddress")]
            target_address: String,
            #[serde(flatten)]
            options: Option<MintOptions>,
        }
        self.request("POST", "/transfers/mint", Some(Request {
            proof,
            target_address: target_address.to_string(),
            options,
        })).await
    }

    // ==================== Burn-Unlock Flow ====================

    /// Burn wrapped tokens
    pub async fn burn(
        &self,
        wrapped_asset: &str,
        amount: &str,
        options: Option<BurnOptions>,
    ) -> Result<BurnReceipt> {
        #[derive(Serialize)]
        struct Request {
            #[serde(rename = "wrappedAsset")]
            wrapped_asset: String,
            amount: String,
            #[serde(flatten)]
            options: Option<BurnOptions>,
        }
        self.request("POST", "/transfers/burn", Some(Request {
            wrapped_asset: wrapped_asset.to_string(),
            amount: amount.to_string(),
            options,
        })).await
    }

    /// Get burn proof for unlocking
    pub async fn get_burn_proof(&self, burn_receipt_id: &str) -> Result<BurnProof> {
        self.request(
            "GET",
            &format!("/transfers/burn/{}/proof", urlencoding::encode(burn_receipt_id)),
            Option::<()>::None,
        ).await
    }

    /// Wait for burn proof to be ready
    pub async fn wait_for_burn_proof(
        &self,
        burn_receipt_id: &str,
        poll_interval: Option<Duration>,
        max_wait: Option<Duration>,
    ) -> Result<BurnProof> {
        let poll_interval = poll_interval.unwrap_or(Duration::from_secs(5));
        let max_wait = max_wait.unwrap_or(Duration::from_secs(600));
        let deadline = std::time::Instant::now() + max_wait;

        loop {
            if std::time::Instant::now() >= deadline {
                return Err(BridgeError::new("Timeout waiting for burn proof")
                    .with_code("CONFIRMATIONS_PENDING"));
            }

            match self.get_burn_proof(burn_receipt_id).await {
                Ok(proof) => return Ok(proof),
                Err(e) if e.is_confirmations_pending() => {
                    tokio::time::sleep(poll_interval).await;
                }
                Err(e) => return Err(e),
            }
        }
    }

    /// Unlock original tokens
    pub async fn unlock(&self, proof: &BurnProof, options: Option<UnlockOptions>) -> Result<SignedTransaction> {
        #[derive(Serialize)]
        struct Request<'a> {
            proof: &'a BurnProof,
            #[serde(flatten)]
            options: Option<UnlockOptions>,
        }
        self.request("POST", "/transfers/unlock", Some(Request { proof, options })).await
    }

    // ==================== Transfer Management ====================

    /// Get transfer by ID
    pub async fn get_transfer(&self, transfer_id: &str) -> Result<Transfer> {
        self.request(
            "GET",
            &format!("/transfers/{}", urlencoding::encode(transfer_id)),
            Option::<()>::None,
        ).await
    }

    /// Get transfer status
    pub async fn get_transfer_status(&self, transfer_id: &str) -> Result<TransferStatus> {
        let transfer = self.get_transfer(transfer_id).await?;
        Ok(transfer.status)
    }

    /// List transfers
    pub async fn list_transfers(&self, filter: Option<TransferFilter>) -> Result<Vec<Transfer>> {
        let mut params = vec![];
        if let Some(f) = &filter {
            if let Some(status) = f.status {
                params.push(format!("status={:?}", status).to_lowercase());
            }
            if let Some(chain) = f.source_chain {
                params.push(format!("sourceChain={:?}", chain).to_lowercase());
            }
            if let Some(chain) = f.target_chain {
                params.push(format!("targetChain={:?}", chain).to_lowercase());
            }
            if let Some(ref asset) = f.asset {
                params.push(format!("asset={}", urlencoding::encode(asset)));
            }
            if let Some(ref sender) = f.sender {
                params.push(format!("sender={}", urlencoding::encode(sender)));
            }
            if let Some(ref recipient) = f.recipient {
                params.push(format!("recipient={}", urlencoding::encode(recipient)));
            }
            if let Some(from_date) = f.from_date {
                params.push(format!("fromDate={}", from_date));
            }
            if let Some(to_date) = f.to_date {
                params.push(format!("toDate={}", to_date));
            }
            if let Some(limit) = f.limit {
                params.push(format!("limit={}", limit));
            }
            if let Some(offset) = f.offset {
                params.push(format!("offset={}", offset));
            }
        }

        let path = if params.is_empty() {
            "/transfers".to_string()
        } else {
            format!("/transfers?{}", params.join("&"))
        };

        #[derive(Deserialize)]
        struct Response {
            transfers: Vec<Transfer>,
        }
        let resp: Response = self.request("GET", &path, Option::<()>::None).await?;
        Ok(resp.transfers)
    }

    /// Wait for transfer to complete
    pub async fn wait_for_transfer(
        &self,
        transfer_id: &str,
        poll_interval: Option<Duration>,
        max_wait: Option<Duration>,
    ) -> Result<Transfer> {
        let poll_interval = poll_interval.unwrap_or(Duration::from_secs(10));
        let max_wait = max_wait.unwrap_or(Duration::from_secs(1800));
        let deadline = std::time::Instant::now() + max_wait;

        loop {
            if std::time::Instant::now() >= deadline {
                return Err(BridgeError::new("Timeout waiting for transfer completion"));
            }

            let transfer = self.get_transfer(transfer_id).await?;
            match transfer.status {
                TransferStatus::Completed | TransferStatus::Failed | TransferStatus::Refunded => {
                    return Ok(transfer);
                }
                _ => {
                    tokio::time::sleep(poll_interval).await;
                }
            }
        }
    }

    // ==================== Convenience Methods ====================

    /// Execute complete lock-mint transfer
    pub async fn bridge_to(
        &self,
        asset: &str,
        amount: &str,
        target_chain: ChainId,
        target_address: &str,
        lock_options: Option<LockOptions>,
        mint_options: Option<MintOptions>,
    ) -> Result<Transfer> {
        // Lock on source chain
        let lock_receipt = self.lock(asset, amount, target_chain, lock_options).await?;

        if self.config.debug {
            eprintln!("Locked: {}, waiting for confirmations...", lock_receipt.id);
        }

        // Wait for proof
        let proof = self.wait_for_lock_proof(&lock_receipt.id, None, None).await?;

        if self.config.debug {
            eprintln!("Proof ready, minting on {:?}...", target_chain);
        }

        // Mint on target chain
        self.mint(&proof, target_address, mint_options).await?;

        // Return final transfer status
        self.wait_for_transfer(&lock_receipt.id, None, None).await
    }

    /// Execute complete burn-unlock transfer
    pub async fn bridge_back(
        &self,
        wrapped_asset: &str,
        amount: &str,
        burn_options: Option<BurnOptions>,
        unlock_options: Option<UnlockOptions>,
    ) -> Result<Transfer> {
        // Burn wrapped tokens
        let burn_receipt = self.burn(wrapped_asset, amount, burn_options).await?;

        if self.config.debug {
            eprintln!("Burned: {}, waiting for confirmations...", burn_receipt.id);
        }

        // Wait for proof
        let proof = self.wait_for_burn_proof(&burn_receipt.id, None, None).await?;

        if self.config.debug {
            eprintln!("Proof ready, unlocking on {:?}...", burn_receipt.target_chain);
        }

        // Unlock on original chain
        self.unlock(&proof, unlock_options).await?;

        // Return final transfer status
        self.wait_for_transfer(&burn_receipt.id, None, None).await
    }

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

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

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

    async fn request<B: Serialize, R: DeserializeOwned>(
        &self,
        method: &str,
        path: &str,
        body: Option<B>,
    ) -> Result<R> {
        if self.is_closed() {
            return Err(BridgeError::new("Client has been 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) => {
                    if self.config.debug {
                        eprintln!("Attempt {} failed: {}", attempt + 1, e);
                    }
                    last_error = Some(e);
                    if attempt < self.config.retries - 1 {
                        tokio::time::sleep(Duration::from_secs((attempt + 1) as u64)).await;
                    }
                }
            }
        }

        Err(last_error.unwrap_or_else(|| BridgeError::new("Unknown error after retries")))
    }

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

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

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

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

        if !status.is_success() {
            let error_body: serde_json::Value = response.json().await.unwrap_or_default();
            let message = error_body["message"]
                .as_str()
                .or_else(|| error_body["error"].as_str())
                .unwrap_or(&format!("HTTP {}", status.as_u16()));

            return Err(BridgeError::new(message)
                .with_status(status.as_u16())
                .with_code(error_body["code"].as_str().unwrap_or("").to_string()));
        }

        let result = response.json().await?;
        Ok(result)
    }
}
