//! Synor RPC SDK Client.

use std::time::Duration;

use reqwest::{Client, header};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

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

/// Synor RPC client.
#[derive(Debug, Clone)]
pub struct SynorRpc {
    config: RpcConfig,
    client: Client,
}

#[derive(Debug, Deserialize)]
struct ApiError {
    message: String,
    code: Option<String>,
}

#[derive(Debug, Deserialize)]
struct BlockResponse {
    block: Block,
}

#[derive(Debug, Deserialize)]
struct BlockHeaderResponse {
    header: BlockHeader,
}

#[derive(Debug, Deserialize)]
struct TransactionResponse {
    transaction: Transaction,
}

#[derive(Debug, Deserialize)]
struct SendTxResponse {
    txid: String,
}

#[derive(Debug, Deserialize)]
struct UtxosResponse {
    utxos: Vec<Utxo>,
}

impl SynorRpc {
    /// Create a new RPC client with the given API key.
    pub fn new(api_key: impl Into<String>) -> Self {
        Self::with_config(RpcConfig::new(api_key))
    }

    /// Create a new RPC client with custom configuration.
    pub fn with_config(config: RpcConfig) -> Self {
        let client = Client::builder()
            .timeout(Duration::from_secs(config.timeout_secs))
            .build()
            .expect("Failed to create HTTP client");

        Self { config, client }
    }

    /// Get a block by hash.
    pub async fn get_block_by_hash(&self, hash: &str) -> Result<Block> {
        let path = format!("/blocks/{}", hash);
        self.request("GET", &path, Option::<&()>::None).await
    }

    /// Get a block by height.
    pub async fn get_block_by_height(&self, height: i64) -> Result<Block> {
        let path = format!("/blocks/height/{}", height);
        self.request("GET", &path, Option::<&()>::None).await
    }

    /// Get the latest block.
    pub async fn get_latest_block(&self) -> Result<Block> {
        self.request("GET", "/blocks/latest", Option::<&()>::None)
            .await
    }

    /// Get a block header by hash.
    pub async fn get_block_header_by_hash(&self, hash: &str) -> Result<BlockHeader> {
        let path = format!("/blocks/{}/header", hash);
        let resp: BlockHeaderResponse = self.request("GET", &path, Option::<&()>::None).await?;
        Ok(resp.header)
    }

    /// Get a block header by height.
    pub async fn get_block_header_by_height(&self, height: i64) -> Result<BlockHeader> {
        let path = format!("/blocks/height/{}/header", height);
        let resp: BlockHeaderResponse = self.request("GET", &path, Option::<&()>::None).await?;
        Ok(resp.header)
    }

    /// Get a transaction by ID.
    pub async fn get_transaction(&self, txid: &str) -> Result<Transaction> {
        let path = format!("/transactions/{}", txid);
        self.request("GET", &path, Option::<&()>::None).await
    }

    /// Send a raw transaction.
    pub async fn send_raw_transaction(&self, raw_tx: &str) -> Result<String> {
        #[derive(Serialize)]
        struct SendTxRequest<'a> {
            raw: &'a str,
        }

        let resp: SendTxResponse = self
            .request("POST", "/transactions", Some(&SendTxRequest { raw: raw_tx }))
            .await?;
        Ok(resp.txid)
    }

    /// Estimate transaction fee.
    pub async fn estimate_fee(&self, priority: Priority) -> Result<FeeEstimate> {
        let priority_str = match priority {
            Priority::Low => "low",
            Priority::Medium => "medium",
            Priority::High => "high",
            Priority::Urgent => "urgent",
        };
        let path = format!("/fees/estimate?priority={}", priority_str);
        self.request("GET", &path, Option::<&()>::None).await
    }

    /// Get all fee estimates.
    pub async fn get_all_fee_estimates(&self) -> Result<Vec<FeeEstimate>> {
        #[derive(Deserialize)]
        struct EstimatesResponse {
            estimates: Vec<FeeEstimate>,
        }

        let resp: EstimatesResponse = self
            .request("GET", "/fees/estimate/all", Option::<&()>::None)
            .await?;
        Ok(resp.estimates)
    }

    /// Get chain information.
    pub async fn get_chain_info(&self) -> Result<ChainInfo> {
        self.request("GET", "/chain", Option::<&()>::None).await
    }

    /// Get mempool information.
    pub async fn get_mempool_info(&self) -> Result<MempoolInfo> {
        self.request("GET", "/mempool", Option::<&()>::None).await
    }

    /// Get UTXOs for an address.
    pub async fn get_utxos(&self, address: &str) -> Result<Vec<Utxo>> {
        let path = format!("/addresses/{}/utxos", address);
        let resp: UtxosResponse = self.request("GET", &path, Option::<&()>::None).await?;
        Ok(resp.utxos)
    }

    /// Get balance for an address.
    pub async fn get_balance(&self, address: &str) -> Result<Balance> {
        let path = format!("/addresses/{}/balance", address);
        self.request("GET", &path, Option::<&()>::None).await
    }

    /// Get blocks in a range.
    pub async fn get_blocks(&self, start_height: i64, end_height: i64) -> Result<Vec<Block>> {
        #[derive(Deserialize)]
        struct BlocksResponse {
            blocks: Vec<Block>,
        }

        let path = format!("/blocks?start={}&end={}", start_height, end_height);
        let resp: BlocksResponse = self.request("GET", &path, Option::<&()>::None).await?;
        Ok(resp.blocks)
    }

    /// Get transactions for an address.
    pub async fn get_address_transactions(
        &self,
        address: &str,
        limit: Option<i32>,
        offset: Option<i32>,
    ) -> Result<Vec<Transaction>> {
        #[derive(Deserialize)]
        struct TxsResponse {
            transactions: Vec<Transaction>,
        }

        let mut path = format!("/addresses/{}/transactions", address);
        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 = format!("{}?{}", path, params.join("&"));
        }

        let resp: TxsResponse = self.request("GET", &path, Option::<&()>::None).await?;
        Ok(resp.transactions)
    }

    /// Get mempool transactions.
    pub async fn get_mempool_transactions(&self, limit: Option<i32>) -> Result<Vec<String>> {
        #[derive(Deserialize)]
        struct MempoolTxsResponse {
            transactions: Vec<String>,
        }

        let path = match limit {
            Some(l) => format!("/mempool/transactions?limit={}", l),
            None => "/mempool/transactions".to_string(),
        };

        let resp: MempoolTxsResponse = self.request("GET", &path, Option::<&()>::None).await?;
        Ok(resp.transactions)
    }

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

        if self.config.debug {
            println!("[SynorRpc] {} {}", method, path);
        }

        let network_str = match self.config.network {
            Network::Mainnet => "mainnet",
            Network::Testnet => "testnet",
        };

        let mut request = match method {
            "GET" => self.client.get(&url),
            "POST" => self.client.post(&url),
            "PUT" => self.client.put(&url),
            "DELETE" => self.client.delete(&url),
            _ => return Err(RpcError::Validation(format!("Invalid method: {}", method))),
        };

        request = request
            .header(header::AUTHORIZATION, format!("Bearer {}", self.config.api_key))
            .header(header::CONTENT_TYPE, "application/json")
            .header("X-Network", network_str);

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

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

        if status.is_client_error() || status.is_server_error() {
            let error: ApiError = response.json().await.unwrap_or(ApiError {
                message: "Unknown error".to_string(),
                code: None,
            });
            return Err(RpcError::Api {
                message: error.message,
                status_code: status.as_u16(),
                code: error.code,
            });
        }

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

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_config_builder() {
        let config = RpcConfig::new("test-key")
            .endpoint("https://custom.endpoint")
            .network(Network::Testnet)
            .timeout(60)
            .debug(true);

        assert_eq!(config.api_key, "test-key");
        assert_eq!(config.endpoint, "https://custom.endpoint");
        assert_eq!(config.network, Network::Testnet);
        assert_eq!(config.timeout_secs, 60);
        assert!(config.debug);
    }

    #[test]
    fn test_client_creation() {
        let client = SynorRpc::new("test-key");
        assert_eq!(client.config.api_key, "test-key");
    }
}
