use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256, Sha512};
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};

use super::error::KrakenError;
use super::types::*;

type HmacSha512 = Hmac<Sha512>;
type Params = HashMap<String, String>;

const DEFAULT_BASE_URL: &str = "https://api.kraken.com";

#[derive(Clone)]
pub struct KrakenClient {
    http: reqwest::Client,
    api_key: Option<String>,
    api_secret: Option<String>,
    base_url: String,
}

impl KrakenClient {
    pub fn from_env() -> Self {
        Self {
            http: reqwest::Client::new(),
            api_key: std::env::var("KRAKEN_API_KEY").ok(),
            api_secret: std::env::var("KRAKEN_API_SECRET").ok(),
            base_url: DEFAULT_BASE_URL.to_string(),
        }
    }

    #[cfg(test)]
    #[allow(dead_code)]
    pub fn new_with_url(
        base_url: String,
        api_key: Option<String>,
        api_secret: Option<String>,
    ) -> Self {
        Self {
            http: reqwest::Client::new(),
            api_key,
            api_secret,
            base_url,
        }
    }

    fn require_auth(&self) -> Result<(&str, &str), KrakenError> {
        match (&self.api_key, &self.api_secret) {
            (Some(key), Some(secret)) => Ok((key.as_str(), secret.as_str())),
            _ => Err(KrakenError::AuthRequired),
        }
    }

    fn nonce() -> u64 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64
    }

    fn sign(path: &str, nonce: u64, post_data: &str, secret: &str) -> Result<String, KrakenError> {
        let secret_bytes = BASE64
            .decode(secret)
            .map_err(|e| KrakenError::Api(format!("Invalid API secret: {e}")))?;

        let mut sha256 = Sha256::new();
        sha256.update(format!("{nonce}{post_data}"));
        let sha256_digest = sha256.finalize();

        let mut hmac = HmacSha512::new_from_slice(&secret_bytes)
            .map_err(|e| KrakenError::Api(format!("HMAC error: {e}")))?;
        hmac.update(path.as_bytes());
        hmac.update(&sha256_digest);

        Ok(BASE64.encode(hmac.finalize().into_bytes()))
    }

    async fn public<T: serde::de::DeserializeOwned>(
        &self,
        method: &str,
        params: &Params,
    ) -> Result<T, KrakenError> {
        let url = format!("{}/0/public/{method}", self.base_url);
        let resp = self.http.get(&url).query(params).send().await?;
        let body: KrakenResponse<T> = resp.json().await?;
        if !body.error.is_empty() {
            return Err(KrakenError::from_api_errors(body.error));
        }
        body.result
            .ok_or_else(|| KrakenError::InvalidResponse("Missing result".into()))
    }

    async fn private<T: serde::de::DeserializeOwned>(
        &self,
        method: &str,
        mut params: Params,
    ) -> Result<T, KrakenError> {
        let (api_key, api_secret) = self.require_auth()?;
        let nonce = Self::nonce();
        params.insert("nonce".into(), nonce.to_string());

        let path = format!("/0/private/{method}");
        let post_data = serde_urlencoded::to_string(&params)
            .map_err(|e| KrakenError::Api(format!("Encode error: {e}")))?;
        let signature = Self::sign(&path, nonce, &post_data, api_secret)?;

        let url = format!("{}{path}", self.base_url);
        let resp = self
            .http
            .post(&url)
            .header("API-Key", api_key)
            .header("API-Sign", &signature)
            .header("Content-Type", "application/x-www-form-urlencoded")
            .body(post_data)
            .send()
            .await?;

        let body: KrakenResponse<T> = resp.json().await?;
        if !body.error.is_empty() {
            return Err(KrakenError::from_api_errors(body.error));
        }
        body.result
            .ok_or_else(|| KrakenError::InvalidResponse("Missing result".into()))
    }

    // === Public ===

    pub async fn ticker(&self, pair: &str) -> Result<TickerResult, KrakenError> {
        let mut p: Params = HashMap::new();
        p.insert("pair".into(), pair.into());
        self.public("Ticker", &p).await
    }

    pub async fn orderbook(
        &self,
        pair: &str,
        count: Option<u32>,
    ) -> Result<OrderBook, KrakenError> {
        let mut p: Params = HashMap::new();
        p.insert("pair".into(), pair.into());
        if let Some(c) = count {
            p.insert("count".into(), c.to_string());
        }

        let url = format!("{}/0/public/Depth", self.base_url);
        let resp = self.http.get(&url).query(&p).send().await?;
        let body: KrakenResponse<serde_json::Value> = resp.json().await?;
        if !body.error.is_empty() {
            return Err(KrakenError::from_api_errors(body.error));
        }
        let result = body
            .result
            .ok_or_else(|| KrakenError::InvalidResponse("Missing result".into()))?;

        let pair_data = result
            .as_object()
            .and_then(|m| m.values().next())
            .ok_or_else(|| KrakenError::InvalidResponse("Empty orderbook".into()))?;

        fn parse_levels(val: &serde_json::Value) -> Vec<OrderBookLevel> {
            val.as_array()
                .map(|arr| {
                    arr.iter()
                        .filter_map(|entry| {
                            let a = entry.as_array()?;
                            Some(OrderBookLevel {
                                price: a.first()?.as_str()?.to_string(),
                                volume: a.get(1)?.as_str()?.to_string(),
                                timestamp: a.get(2)?.as_u64().unwrap_or(0),
                            })
                        })
                        .collect()
                })
                .unwrap_or_default()
        }

        Ok(OrderBook {
            asks: parse_levels(&pair_data["asks"]),
            bids: parse_levels(&pair_data["bids"]),
        })
    }

    pub async fn ohlc(
        &self,
        pair: &str,
        interval: Option<u32>,
        since: Option<u64>,
    ) -> Result<serde_json::Value, KrakenError> {
        let mut p: Params = HashMap::new();
        p.insert("pair".into(), pair.into());
        if let Some(i) = interval {
            p.insert("interval".into(), i.to_string());
        }
        if let Some(s) = since {
            p.insert("since".into(), s.to_string());
        }
        self.public("OHLC", &p).await
    }

    // === Private ===

    pub async fn balance(&self) -> Result<BalanceResult, KrakenError> {
        self.private("Balance", Params::new()).await
    }

    pub async fn trade_history(
        &self,
        offset: Option<u32>,
    ) -> Result<TradesHistoryResult, KrakenError> {
        let mut p = Params::new();
        if let Some(ofs) = offset {
            p.insert("ofs".into(), ofs.to_string());
        }
        self.private("TradesHistory", p).await
    }

    pub async fn open_orders(&self) -> Result<OpenOrdersResult, KrakenError> {
        self.private("OpenOrders", Params::new()).await
    }

    pub async fn add_order(
        &self,
        pair: &str,
        direction: &str,
        order_type: &str,
        volume: &str,
        price: Option<&str>,
        validate: bool,
    ) -> Result<AddOrderResult, KrakenError> {
        let mut p = Params::new();
        p.insert("pair".into(), pair.into());
        p.insert("type".into(), direction.into());
        p.insert("ordertype".into(), order_type.into());
        p.insert("volume".into(), volume.into());
        if let Some(pr) = price {
            p.insert("price".into(), pr.into());
        }
        if validate {
            p.insert("validate".into(), "true".into());
        }
        self.private("AddOrder", p).await
    }

    pub async fn cancel_order(&self, txid: &str) -> Result<CancelOrderResult, KrakenError> {
        let mut p = Params::new();
        p.insert("txid".into(), txid.into());
        self.private("CancelOrder", p).await
    }
}
