use crate::proto::*;
use reqwest::{Client, Url};
use rice_core::error::RiceError;
use serde_json::Value;

#[derive(Clone)]
pub struct HttpClient {
    client: Client,
    base_url: Url,
    token: Option<String>,
}

impl HttpClient {
    pub fn new_with_url(url: &str, token: Option<String>) -> Result<Self, RiceError> {
        let base_url = Url::parse(url)
            .map_err(|e| RiceError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)))?;

        let mut headers = reqwest::header::HeaderMap::new();
        if let Some(ref t) = token {
            let mut auth_value = reqwest::header::HeaderValue::from_str(&format!("Bearer {}", t))
                .map_err(|e| {
                RiceError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
            })?;
            auth_value.set_sensitive(true);
            headers.insert(reqwest::header::AUTHORIZATION, auth_value);
        }

        let client = Client::builder().default_headers(headers).build()?;

        Ok(Self {
            client,
            base_url,
            token,
        })
    }

    pub fn new(host: &str, port: u16, token: Option<String>) -> Result<Self, RiceError> {
        Self::new_with_url(&format!("http://{}:{}/", host, port), token)
    }

    pub async fn connect(&self) -> Result<bool, RiceError> {
        match self.health().await {
            Ok(res) => Ok(res.status == "ok"),
            Err(_) => Ok(false),
        }
    }

    pub async fn health(&self) -> Result<HealthResponse, RiceError> {
        let url = self
            .base_url
            .join("health")
            .map_err(|e| RiceError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)))?;
        let res = self.client.get(url).send().await?;

        if !res.status().is_success() {
            return Err(RiceError::Http(res.error_for_status().unwrap_err()));
        }

        let text = res.text().await?;

        match serde_json::from_str::<Value>(&text) {
            Ok(body) => Ok(HealthResponse {
                status: body["status"].as_str().unwrap_or("ok").to_string(),
                version: body["version"].as_str().unwrap_or("unknown").to_string(),
            }),
            Err(_) => Ok(HealthResponse {
                status: "ok".to_string(),
                version: "unknown".to_string(),
            }),
        }
    }

    pub async fn login(
        &mut self,
        username: String,
        password: String,
    ) -> Result<LoginResponse, RiceError> {
        let url = self
            .base_url
            .join("auth/login")
            .map_err(|e| RiceError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)))?;
        let payload = serde_json::json!({
            "username": username,
            "password": password,
        });

        let res = self.client.post(url).json(&payload).send().await?;

        if !res.status().is_success() {
            return Err(RiceError::Http(res.error_for_status().unwrap_err()));
        }

        let body: Value = res.json().await?;
        let token = body["token"].as_str().unwrap_or("").to_string();

        // Update token in client
        self.token = Some(token.clone());

        let mut headers = reqwest::header::HeaderMap::new();
        let mut auth_value = reqwest::header::HeaderValue::from_str(&format!("Bearer {}", token))
            .map_err(|e| {
            RiceError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
        })?;
        auth_value.set_sensitive(true);
        headers.insert(reqwest::header::AUTHORIZATION, auth_value);

        self.client = Client::builder().default_headers(headers).build()?;

        Ok(LoginResponse {
            token,
            user_id: body["user_id"].as_u64().unwrap_or(0),
            role: body["role"].as_str().unwrap_or("").to_string(),
        })
    }

    pub async fn insert(
        &self,
        id: u64,
        text: String,
        metadata: Vec<u8>,
        user_id: u64,
        session_id: Option<String>,
    ) -> Result<InsertResponse, RiceError> {
        let meta_json: Value = if metadata.is_empty() {
            serde_json::json!({})
        } else {
            serde_json::from_slice(&metadata).unwrap_or(serde_json::json!({}))
        };

        let mut payload = serde_json::json!({
            "id": id,
            "text": text,
            "metadata": meta_json,
            "user_id": user_id,
        });

        if let Some(sid) = session_id {
            payload["session_id"] = serde_json::Value::String(sid);
        }

        let url = self
            .base_url
            .join("insert")
            .map_err(|e| RiceError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)))?;
        let res = self.client.post(url).json(&payload).send().await?;

        if !res.status().is_success() {
            return Err(RiceError::Http(res.error_for_status().unwrap_err()));
        }

        let body: Value = res.json().await?;

        Ok(InsertResponse {
            success: body["success"].as_bool().unwrap_or(false),
            node_id: body["node_id"].as_u64().unwrap_or(0),
            message: body["message"].as_str().unwrap_or("").to_string(),
        })
    }

    pub async fn search(
        &self,
        query_text: String,
        user_id: u64,
        k: u32,
        session_id: Option<String>,
        filter: Option<String>,
    ) -> Result<SearchResponse, RiceError> {
        let mut payload = serde_json::json!({
            "query": query_text, // Updated to match TypeScript which used 'query'? No, I kept 'query' here but 'query_text' in param.
            "user_id": user_id,
            "k": k,
        });

        if let Some(sid) = session_id {
            payload["session_id"] = serde_json::Value::String(sid);
        }

        if let Some(f) = filter {
            if let Ok(filter_json) = serde_json::from_str::<Value>(&f) {
                payload["filter"] = filter_json;
            }
        }

        let url = self
            .base_url
            .join("search")
            .map_err(|e| RiceError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)))?;
        let res = self.client.post(url).json(&payload).send().await?;

        if !res.status().is_success() {
            return Err(RiceError::Http(res.error_for_status().unwrap_err()));
        }

        let body: Value = res.json().await?;
        let empty_vec = vec![];
        let results_array = if body.is_array() {
            body.as_array().unwrap()
        } else {
            body["results"].as_array().unwrap_or(&empty_vec)
        };

        let mut results = Vec::new();
        for r in results_array {
            let metadata_val = r["metadata"].clone();
            // Convert metadata back to bytes for SearchResult
            let metadata_bytes = serde_json::to_vec(&metadata_val).unwrap_or_default();

            results.push(SearchResult {
                id: r["id"].as_u64().unwrap_or(0),
                similarity: r["similarity"].as_f64().unwrap_or(0.0) as f32,
                metadata: metadata_bytes,
            });
        }

        Ok(SearchResponse { results })
    }
}
