use anyhow::{Context, Result};
use async_trait::async_trait;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use super::{Database, SearchResult, VectorStore};

pub struct QdrantDb {
    client: Client,
    base_url: String,
    api_key: String,
    collection_name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorPayload {
    pub uuid: String,
    pub chunk_id: String,
    pub chunk_type: String,
    pub start_time: f64,
    pub end_time: f64,
    pub text: Option<String>,
}

impl QdrantDb {
    pub fn new() -> Self {
        let base_url =
            std::env::var("QDRANT_URL").unwrap_or_else(|_| "http://localhost:6333".to_string());
        let api_key = std::env::var("QDRANT_API_KEY")
            .unwrap_or_else(|_| "Test3200Test3200Test3200".to_string());
        let collection_name =
            std::env::var("QDRANT_COLLECTION").unwrap_or_else(|_| "momentry_rule1".to_string());

        tracing::debug!(
            "QdrantDb::new() - base_url: {}, collection_name: {}",
            base_url,
            collection_name
        );

        Self {
            client: Client::new(),
            base_url,
            api_key,
            collection_name,
        }
    }
}

impl Default for QdrantDb {
    fn default() -> Self {
        Self::new()
    }
}

impl QdrantDb {
    pub async fn init_collection(&self, vector_dim: usize) -> Result<()> {
        let url = format!("{}/collections/{}", self.base_url, self.collection_name);

        let response = self
            .client
            .get(&url)
            .header("api-key", &self.api_key)
            .send()
            .await?;

        if response.status().is_success() {
            return Ok(());
        }

        let create_url = format!("{}/collections", self.base_url);
        let body = serde_json::json!({
            "vectors": {
                "size": vector_dim,
                "distance": "Cosine"
            }
        });

        self.client
            .post(&create_url)
            .header("api-key", &self.api_key)
            .header("Content-Type", "application/json")
            .json(&body)
            .send()
            .await
            .context("Failed to create Qdrant collection")?;

        Ok(())
    }

    pub async fn upsert_vector(
        &self,
        chunk_id: &str,
        vector: &[f32],
        payload: VectorPayload,
    ) -> Result<()> {
        let url = format!(
            "{}/collections/{}/points?wait=true",
            self.base_url, self.collection_name
        );

        tracing::debug!(
            "Qdrant upsert URL: {}, collection_name: {}",
            url,
            self.collection_name
        );

        let mut payload_map = HashMap::new();
        payload_map.insert("uuid".to_string(), serde_json::json!(payload.uuid));
        payload_map.insert("chunk_id".to_string(), serde_json::json!(payload.chunk_id));
        payload_map.insert(
            "chunk_type".to_string(),
            serde_json::json!(payload.chunk_type),
        );
        payload_map.insert(
            "start_time".to_string(),
            serde_json::json!(payload.start_time),
        );
        payload_map.insert("end_time".to_string(), serde_json::json!(payload.end_time));
        if let Some(text) = payload.text {
            payload_map.insert("text".to_string(), serde_json::json!(text));
        }

        // Generate consistent point ID from uuid and chunk_id
        // Qdrant requires integer or UUID point IDs. We'll use a simple integer hash.
        let point_id_str = format!("{}_{}", payload.uuid, chunk_id);
        use std::collections::hash_map::DefaultHasher;
        use std::hash::{Hash, Hasher};
        let mut hasher = DefaultHasher::new();
        point_id_str.hash(&mut hasher);
        let point_id = hasher.finish();

        let body = serde_json::json!({
            "points": [{
                "id": point_id,
                "vector": vector,
                "payload": payload_map
            }]
        });

        tracing::debug!(
            "Upserting vector to Qdrant: chunk_id={}, uuid={}, vector_len={}",
            chunk_id,
            payload.uuid,
            vector.len()
        );

        let response = self
            .client
            .put(&url)
            .header("api-key", &self.api_key)
            .header("Content-Type", "application/json")
            .json(&body)
            .send()
            .await
            .context("Failed to send upsert request to Qdrant")?;

        // Check response status
        let status = response.status();
        let response_text = response
            .text()
            .await
            .unwrap_or_else(|_| "Failed to read response".to_string());

        if !status.is_success() {
            tracing::error!("Qdrant upsert failed: {} - {}", status, response_text);
            return Err(anyhow::anyhow!(
                "Qdrant upsert failed with status {}: {}",
                status,
                response_text
            ));
        }

        tracing::debug!("Qdrant upsert response status: {}", status);
        tracing::info!("Successfully upserted vector for chunk: {}", chunk_id);
        Ok(())
    }

    pub async fn search(&self, query_vector: &[f32], limit: usize) -> Result<Vec<SearchResult>> {
        let url = format!(
            "{}/collections/{}/points/search",
            self.base_url, self.collection_name
        );

        let body = serde_json::json!({
            "vector": query_vector,
            "limit": limit,
            "with_payload": true
        });

        let response = self
            .client
            .post(&url)
            .header("api-key", &self.api_key)
            .header("Content-Type", "application/json")
            .json(&body)
            .send()
            .await
            .context("Failed to search Qdrant")?;

        // Check response status
        let status = response.status();
        let response_text = response
            .text()
            .await
            .unwrap_or_else(|_| "Failed to read response".to_string());

        if !status.is_success() {
            tracing::error!("Qdrant search failed: {} - {}", status, response_text);
            return Err(anyhow::anyhow!(
                "Qdrant search failed with status {}: {}",
                status,
                response_text
            ));
        }

        #[derive(Deserialize)]
        struct QdrantSearchResult {
            result: Vec<QdrantPoint>,
        }

        #[derive(Deserialize)]
        struct QdrantPoint {
            #[allow(dead_code)]
            id: serde_json::Value,
            score: f64,
            payload: HashMap<String, serde_json::Value>,
        }

        let result: QdrantSearchResult = serde_json::from_str(&response_text)
            .context("Failed to parse Qdrant search response")?;

        let search_results: Vec<SearchResult> = result
            .result
            .into_iter()
            .map(|r| {
                let uuid = r
                    .payload
                    .get("uuid")
                    .and_then(|v| v.as_str())
                    .unwrap_or("unknown")
                    .to_string();
                let chunk_id = r
                    .payload
                    .get("chunk_id")
                    .and_then(|v| v.as_str())
                    .unwrap_or("unknown")
                    .to_string();
                SearchResult {
                    uuid,
                    chunk_id,
                    score: r.score as f32,
                }
            })
            .collect();

        Ok(search_results)
    }

    pub async fn search_collections(
        &self,
        query_vector: &[f32],
        collections: &[&str],
        limit: usize,
    ) -> Result<Vec<SearchResult>> {
        let mut handles = Vec::new();
        for &collection in collections {
            let url = format!("{}/collections/{}/points/search", self.base_url, collection);
            let client = self.client.clone();
            let api_key = self.api_key.clone();
            let query_vec = query_vector.to_vec();
            let body = serde_json::json!({
                "vector": query_vec,
                "limit": limit * 2, // Fetch more from each to account for overlaps
                "with_payload": true
            });
            handles.push(async move {
                let response = client
                    .post(&url)
                    .header("api-key", &api_key)
                    .header("Content-Type", "application/json")
                    .json(&body)
                    .send()
                    .await;

                match response {
                    Ok(resp) if resp.status().is_success() => {
                        let resp_text = resp
                            .text()
                            .await
                            .unwrap_or_else(|_| "Failed to read response".to_string());

                        #[derive(Deserialize)]
                        struct QdrantSearchResult {
                            result: Vec<QdrantPoint>,
                        }
                        #[derive(Deserialize)]
                        struct QdrantPoint {
                            #[allow(dead_code)]
                            id: serde_json::Value,
                            score: f64,
                            payload: HashMap<String, serde_json::Value>,
                        }
                        if let Ok(result) = serde_json::from_str::<QdrantSearchResult>(&resp_text) {
                            let results: Vec<SearchResult> = result
                                .result
                                .into_iter()
                                .map(|r| {
                                    let uuid = r
                                        .payload
                                        .get("uuid")
                                        .and_then(|v| v.as_str())
                                        .unwrap_or("unknown")
                                        .to_string();
                                    let chunk_id = r
                                        .payload
                                        .get("chunk_id")
                                        .and_then(|v| v.as_str())
                                        .unwrap_or("unknown")
                                        .to_string();
                                    SearchResult {
                                        uuid,
                                        chunk_id,
                                        score: r.score as f32,
                                    }
                                })
                                .collect();
                            Ok::<Vec<SearchResult>, anyhow::Error>(results)
                        } else {
                            Ok::<Vec<SearchResult>, anyhow::Error>(Vec::new())
                        }
                    }
                    _ => Ok::<Vec<SearchResult>, anyhow::Error>(Vec::new()),
                }
            });
        }

        let results = futures_util::future::join_all(handles).await;
        let mut merged: Vec<SearchResult> = results
            .into_iter()
            .filter_map(Result::ok)
            .flatten()
            .collect();

        // Sort by score descending
        merged.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
        // Deduplicate by chunk_id + uuid
        merged.dedup_by_key(|r| (r.chunk_id.clone(), r.uuid.clone()));
        // Truncate to limit
        merged.truncate(limit);

        Ok(merged)
    }

    pub async fn search_in_uuid(
        &self,
        query_vector: &[f32],
        uuid: &str,
        limit: usize,
    ) -> Result<Vec<SearchResult>> {
        let url = format!(
            "{}/collections/{}/points/search",
            self.base_url, self.collection_name
        );

        let body = serde_json::json!({
            "vector": query_vector,
            "limit": limit,
            "with_payload": true,
            "filter": {
                "must": [
                    {
                        "key": "uuid",
                        "match": {
                            "value": uuid
                        }
                    }
                ]
            }
        });

        let response = self
            .client
            .post(&url)
            .header("api-key", &self.api_key)
            .header("Content-Type", "application/json")
            .json(&body)
            .send()
            .await
            .context("Failed to search Qdrant")?;

        // Check response status
        let status = response.status();
        let response_text = response
            .text()
            .await
            .unwrap_or_else(|_| "Failed to read response".to_string());

        if !status.is_success() {
            tracing::error!(
                "Qdrant search_in_uuid failed: {} - {}",
                status,
                response_text
            );
            return Err(anyhow::anyhow!(
                "Qdrant search_in_uuid failed with status {}: {}",
                status,
                response_text
            ));
        }

        #[derive(Deserialize)]
        struct QdrantSearchResult {
            result: Vec<QdrantPoint>,
        }

        #[derive(Deserialize)]
        struct QdrantPoint {
            #[allow(dead_code)]
            id: serde_json::Value,
            score: f64,
            payload: HashMap<String, serde_json::Value>,
        }

        let result: QdrantSearchResult = serde_json::from_str(&response_text)
            .context("Failed to parse Qdrant search_in_uuid response")?;

        let search_results: Vec<SearchResult> = result
            .result
            .into_iter()
            .map(|r| {
                let uuid = r
                    .payload
                    .get("uuid")
                    .and_then(|v| v.as_str())
                    .unwrap_or("unknown")
                    .to_string();
                let chunk_id = r
                    .payload
                    .get("chunk_id")
                    .and_then(|v| v.as_str())
                    .unwrap_or("unknown")
                    .to_string();
                SearchResult {
                    uuid,
                    chunk_id,
                    score: r.score as f32,
                }
            })
            .collect();

        Ok(search_results)
    }

    pub async fn delete_by_uuid(&self, uuid: &str) -> Result<()> {
        let url = format!(
            "{}/collections/{}/points/delete",
            self.base_url, self.collection_name
        );

        let body = serde_json::json!({
            "filter": {
                "must": [
                    {
                        "key": "uuid",
                        "match": {
                            "value": uuid
                        }
                    }
                ]
            }
        });

        self.client
            .post(&url)
            .header("api-key", &self.api_key)
            .header("Content-Type", "application/json")
            .json(&body)
            .send()
            .await
            .context("Failed to delete points from Qdrant")?;

        Ok(())
    }

    pub async fn get_point_count(&self) -> Result<usize> {
        let url = format!(
            "{}/collections/{}/info",
            self.base_url, self.collection_name
        );

        let response = self
            .client
            .get(&url)
            .header("api-key", &self.api_key)
            .send()
            .await
            .context("Failed to get collection info")?;

        #[derive(Deserialize)]
        struct CollectionInfo {
            result: CollectionStatus,
        }

        #[derive(Deserialize)]
        struct CollectionStatus {
            points_count: usize,
        }

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

#[async_trait]
impl Database for QdrantDb {
    async fn init() -> Result<Self> {
        let db = Self::new();
        db.init_collection(768).await?;
        Ok(db)
    }
}

#[async_trait]
impl VectorStore for QdrantDb {
    async fn store_vector(&self, chunk_id: &str, vector: &[f32]) -> Result<()> {
        let payload = VectorPayload {
            uuid: String::new(),
            chunk_id: chunk_id.to_string(),
            chunk_type: String::new(),
            start_time: 0.0,
            end_time: 0.0,
            text: None,
        };
        self.upsert_vector(chunk_id, vector, payload).await
    }

    async fn search(&self, query_vector: &[f32], limit: usize) -> Result<Vec<SearchResult>> {
        self.search(query_vector, limit).await
    }
}
