//! IBC SDK Client

use crate::ibc::types::*;
use reqwest::Client;
use serde::{de::DeserializeOwned, Serialize};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

/// Synor IBC SDK Client
pub struct SynorIbc {
    config: IbcConfig,
    client: Client,
    closed: Arc<AtomicBool>,
}

impl SynorIbc {
    /// Create a new IBC client
    pub fn new(config: IbcConfig) -> Self {
        Self {
            config,
            client: Client::new(),
            closed: Arc::new(AtomicBool::new(false)),
        }
    }

    /// Get chain ID
    pub fn chain_id(&self) -> &str {
        &self.config.chain_id
    }

    /// Get chain info
    pub async fn get_chain_info(&self) -> IbcResult<serde_json::Value> {
        self.get("/chain").await
    }

    /// Get current height
    pub async fn get_height(&self) -> IbcResult<Height> {
        self.get("/chain/height").await
    }

    /// Health check
    pub async fn health_check(&self) -> bool {
        match self.get::<serde_json::Value>("/health").await {
            Ok(v) => v.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 client is closed
    pub fn is_closed(&self) -> bool {
        self.closed.load(Ordering::SeqCst)
    }

    // ========================================================================
    // Light Client Operations
    // ========================================================================

    /// Create a new light client
    pub async fn create_client(
        &self,
        client_type: ClientType,
        client_state: ClientState,
        consensus_state: ConsensusState,
    ) -> IbcResult<ClientId> {
        #[derive(Deserialize)]
        struct Response { client_id: String }
        let resp: Response = self.post("/clients", &serde_json::json!({
            "client_type": client_type,
            "client_state": client_state,
            "consensus_state": consensus_state,
        })).await?;
        Ok(ClientId(resp.client_id))
    }

    /// Get client state
    pub async fn get_client_state(&self, client_id: &ClientId) -> IbcResult<ClientState> {
        self.get(&format!("/clients/{}/state", client_id.0)).await
    }

    /// List all clients
    pub async fn list_clients(&self) -> IbcResult<Vec<serde_json::Value>> {
        self.get("/clients").await
    }

    // ========================================================================
    // Connection Operations
    // ========================================================================

    /// Initialize connection handshake
    pub async fn connection_open_init(
        &self,
        client_id: &ClientId,
        counterparty_client_id: &ClientId,
    ) -> IbcResult<ConnectionId> {
        #[derive(Deserialize)]
        struct Response { connection_id: String }
        let resp: Response = self.post("/connections/init", &serde_json::json!({
            "client_id": client_id.0,
            "counterparty_client_id": counterparty_client_id.0,
        })).await?;
        Ok(ConnectionId(resp.connection_id))
    }

    /// Get connection
    pub async fn get_connection(&self, connection_id: &ConnectionId) -> IbcResult<ConnectionEnd> {
        self.get(&format!("/connections/{}", connection_id.0)).await
    }

    /// List all connections
    pub async fn list_connections(&self) -> IbcResult<Vec<serde_json::Value>> {
        self.get("/connections").await
    }

    // ========================================================================
    // Channel Operations
    // ========================================================================

    /// Bind a port
    pub async fn bind_port(&self, port_id: &PortId, module: &str) -> IbcResult<()> {
        self.post::<serde_json::Value, _>("/ports/bind", &serde_json::json!({
            "port_id": port_id.0,
            "module": module,
        })).await?;
        Ok(())
    }

    /// Initialize channel handshake
    pub async fn channel_open_init(
        &self,
        port_id: &PortId,
        ordering: ChannelOrder,
        connection_id: &ConnectionId,
        counterparty_port: &PortId,
        version: &str,
    ) -> IbcResult<ChannelId> {
        #[derive(Deserialize)]
        struct Response { channel_id: String }
        let resp: Response = self.post("/channels/init", &serde_json::json!({
            "port_id": port_id.0,
            "ordering": ordering,
            "connection_id": connection_id.0,
            "counterparty_port": counterparty_port.0,
            "version": version,
        })).await?;
        Ok(ChannelId(resp.channel_id))
    }

    /// Get channel
    pub async fn get_channel(&self, port_id: &PortId, channel_id: &ChannelId) -> IbcResult<Channel> {
        self.get(&format!("/channels/{}/{}", port_id.0, channel_id.0)).await
    }

    /// List all channels
    pub async fn list_channels(&self) -> IbcResult<Vec<serde_json::Value>> {
        self.get("/channels").await
    }

    // ========================================================================
    // Packet Operations
    // ========================================================================

    /// Send a packet
    pub async fn send_packet(
        &self,
        source_port: &PortId,
        source_channel: &ChannelId,
        data: &[u8],
        timeout: Timeout,
    ) -> IbcResult<serde_json::Value> {
        use base64::{Engine as _, engine::general_purpose::STANDARD};
        self.post("/packets/send", &serde_json::json!({
            "source_port": source_port.0,
            "source_channel": source_channel.0,
            "data": STANDARD.encode(data),
            "timeout_height": timeout.height,
            "timeout_timestamp": timeout.timestamp,
        })).await
    }

    // ========================================================================
    // Transfer Operations (ICS-20)
    // ========================================================================

    /// Transfer tokens to another chain
    pub async fn transfer(
        &self,
        source_port: &str,
        source_channel: &str,
        denom: &str,
        amount: &str,
        sender: &str,
        receiver: &str,
        timeout: Option<Timeout>,
        memo: Option<&str>,
    ) -> IbcResult<serde_json::Value> {
        let mut body = serde_json::json!({
            "source_port": source_port,
            "source_channel": source_channel,
            "token": { "denom": denom, "amount": amount },
            "sender": sender,
            "receiver": receiver,
        });
        if let Some(t) = timeout {
            body["timeout_height"] = serde_json::to_value(&t.height).unwrap();
            body["timeout_timestamp"] = serde_json::json!(t.timestamp);
        }
        if let Some(m) = memo {
            body["memo"] = serde_json::json!(m);
        }
        self.post("/transfer", &body).await
    }

    /// Get denom trace
    pub async fn get_denom_trace(&self, ibc_denom: &str) -> IbcResult<serde_json::Value> {
        self.get(&format!("/transfer/denom_trace/{}", ibc_denom)).await
    }

    // ========================================================================
    // Swap Operations (HTLC)
    // ========================================================================

    /// Initiate an atomic swap
    pub async fn initiate_swap(
        &self,
        responder: &str,
        initiator_asset: SwapAsset,
        responder_asset: SwapAsset,
    ) -> IbcResult<serde_json::Value> {
        self.post("/swaps/initiate", &serde_json::json!({
            "responder": responder,
            "initiator_asset": initiator_asset,
            "responder_asset": responder_asset,
        })).await
    }

    /// Lock initiator's tokens
    pub async fn lock_swap(&self, swap_id: &SwapId) -> IbcResult<()> {
        self.post::<serde_json::Value, _>(&format!("/swaps/{}/lock", swap_id.0), &()).await?;
        Ok(())
    }

    /// Respond to a swap
    pub async fn respond_to_swap(&self, swap_id: &SwapId, asset: SwapAsset) -> IbcResult<Htlc> {
        self.post(&format!("/swaps/{}/respond", swap_id.0), &serde_json::json!({
            "asset": asset,
        })).await
    }

    /// Claim tokens with secret
    pub async fn claim_swap(&self, swap_id: &SwapId, secret: &[u8]) -> IbcResult<serde_json::Value> {
        use base64::{Engine as _, engine::general_purpose::STANDARD};
        self.post(&format!("/swaps/{}/claim", swap_id.0), &serde_json::json!({
            "secret": STANDARD.encode(secret),
        })).await
    }

    /// Refund expired swap
    pub async fn refund_swap(&self, swap_id: &SwapId) -> IbcResult<serde_json::Value> {
        self.post(&format!("/swaps/{}/refund", swap_id.0), &()).await
    }

    /// Get swap by ID
    pub async fn get_swap(&self, swap_id: &SwapId) -> IbcResult<AtomicSwap> {
        self.get(&format!("/swaps/{}", swap_id.0)).await
    }

    /// List active swaps
    pub async fn list_active_swaps(&self) -> IbcResult<Vec<AtomicSwap>> {
        self.get("/swaps/active").await
    }

    /// Verify secret against hashlock
    pub fn verify_secret(&self, hashlock: &Hashlock, secret: &[u8]) -> bool {
        hashlock.verify(secret)
    }

    // ========================================================================
    // HTTP Methods
    // ========================================================================

    async fn get<T: DeserializeOwned>(&self, path: &str) -> IbcResult<T> {
        self.request("GET", path, Option::<&()>::None).await
    }

    async fn post<T: DeserializeOwned, B: Serialize>(&self, path: &str, body: &B) -> IbcResult<T> {
        self.request("POST", path, Some(body)).await
    }

    async fn request<T: DeserializeOwned, B: Serialize>(
        &self,
        method: &str,
        path: &str,
        body: Option<&B>,
    ) -> IbcResult<T> {
        if self.is_closed() {
            return Err(IbcError {
                message: "Client has been closed".to_string(),
                code: Some("CLIENT_CLOSED".to_string()),
                status: None,
            });
        }

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

        for attempt in 0..=self.config.retries {
            let mut req = match method {
                "GET" => self.client.get(&url),
                "POST" => self.client.post(&url),
                "DELETE" => self.client.delete(&url),
                _ => unreachable!(),
            };

            req = req
                .header("Content-Type", "application/json")
                .header("Authorization", format!("Bearer {}", self.config.api_key))
                .header("X-SDK-Version", "rust/0.1.0")
                .header("X-Chain-Id", &self.config.chain_id)
                .timeout(self.config.timeout);

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

            match req.send().await {
                Ok(resp) => {
                    let status = resp.status();
                    if status.is_success() {
                        return resp.json().await.map_err(|e| IbcError {
                            message: e.to_string(),
                            code: None,
                            status: None,
                        });
                    } else {
                        let error: serde_json::Value = resp.json().await.unwrap_or_default();
                        return Err(IbcError {
                            message: error["message"].as_str().unwrap_or(&format!("HTTP {}", status)).to_string(),
                            code: error["code"].as_str().map(String::from),
                            status: Some(status.as_u16()),
                        });
                    }
                }
                Err(e) => {
                    last_error = Some(e.to_string());
                    if attempt < self.config.retries {
                        tokio::time::sleep(std::time::Duration::from_millis(100 * 2u64.pow(attempt))).await;
                    }
                }
            }
        }

        Err(IbcError {
            message: last_error.unwrap_or_else(|| "Request failed".to_string()),
            code: Some("NETWORK_ERROR".to_string()),
            status: None,
        })
    }
}
