use anyhow::{Context, Result};
use serde_json;

use crate::core::chunk::types::{Chunk, ChunkRule, ChunkType};
use crate::core::db::mongodb_db::MongoDb;
use crate::core::db::postgres_db::PostgresDb;
use crate::core::db::qdrant_db::{QdrantDb, VectorPayload};
use crate::core::processor::asr::{AsrResult, AsrSegment};

pub struct SyncDb {
    postgres: PostgresDb,
    mongodb: MongoDb,
    qdrant: QdrantDb,
}

impl SyncDb {
    pub async fn new(postgres: PostgresDb, mongodb: MongoDb, qdrant: QdrantDb) -> Result<Self> {
        Ok(Self {
            postgres,
            mongodb,
            qdrant,
        })
    }

    pub async fn store_chunk_with_vector(&self, mut chunk: Chunk, text: &str) -> Result<Chunk> {
        let uuid = chunk.uuid.clone();
        let chunk_id = chunk.chunk_id.clone();
        let chunk_type = chunk.chunk_type.as_str().to_string();
        let start_time = chunk.start_time().seconds();
        let end_time = chunk.end_time().seconds();

        let vector = self.embed_text(text).await?;

        let vector_id = format!("vec_{}", chunk_id);
        chunk = chunk.with_vector_id(vector_id.clone());

        let postgres_result = self.postgres.store_chunk(&chunk).await;
        if let Err(e) = &postgres_result {
            tracing::warn!("Failed to store chunk in PostgreSQL: {}", e);
        }

        let mongo_result = self.mongodb.store_chunk(&chunk).await;
        if let Err(e) = &mongo_result {
            tracing::warn!("Failed to store chunk in MongoDB: {}", e);
        }

        let payload = VectorPayload {
            uuid: uuid.clone(),
            chunk_id: chunk_id.clone(),
            chunk_type,
            start_time,
            end_time,
            text: Some(text.to_string()),
        };

        let qdrant_result = self
            .qdrant
            .upsert_vector(&vector_id, &vector, payload)
            .await;
        if let Err(e) = &qdrant_result {
            tracing::warn!("Failed to store vector in Qdrant: {}", e);
        }

        let pg_vector_result = self.postgres.store_vector(&vector_id, &vector, &uuid).await;
        if let Err(e) = &pg_vector_result {
            tracing::warn!("Failed to store vector in PostgreSQL: {}", e);
        }

        postgres_result?;
        mongo_result?;
        qdrant_result?;

        Ok(chunk)
    }

    pub async fn embed_text(&self, text: &str) -> Result<Vec<f32>> {
        let client = reqwest::Client::new();
        let response = client
            .post("http://localhost:11434/api/embeddings")
            .json(&serde_json::json!({
                "model": "all-minilm",
                "prompt": text,
            }))
            .send()
            .await
            .context("Failed to call Ollama embedding API")?;

        #[derive(serde::Deserialize)]
        struct EmbeddingResponse {
            embedding: Vec<f32>,
        }

        let embedding = response
            .json::<EmbeddingResponse>()
            .await
            .context("Failed to parse embedding response")?;

        Ok(embedding.embedding)
    }

    pub async fn process_asr_to_chunks(
        &self,
        uuid: &str,
        asr_result: &AsrResult,
    ) -> Result<Vec<Chunk>> {
        let mut chunks = Vec::new();

        for (i, segment) in asr_result.segments.iter().enumerate() {
            let segment: &AsrSegment = segment;
            let content = serde_json::json!({
                "rule": "rule1",
                "data": {
                    "text": segment.text,
                    "start": segment.start,
                    "end": segment.end,
                },
            });
            let metadata = serde_json::json!({
                "file_uuid": uuid,
                "chunk_type": "sentence",
                "chunk_rule": "rule1",
                "fps": 0.0, // Will be set later
                "start_frame": 0,
                "end_frame": 0,
                "language": asr_result.language,
                "language_probability": asr_result.language_probability,
            });

            let chunk = Chunk::from_seconds(
                0, // file_id - will be set later
                uuid.to_string(),
                i as u32,
                ChunkType::Sentence,
                ChunkRule::Rule1,
                segment.start,
                segment.end,
                24.0, // fps
                content,
            )
            .with_metadata(metadata);

            chunks.push(chunk);
        }

        let mut stored_chunks = Vec::new();
        for chunk in chunks {
            let text = chunk
                .content
                .get("data")
                .and_then(|data| data.get("text"))
                .and_then(|t| t.as_str())
                .unwrap_or("")
                .to_string();

            match self.store_chunk_with_vector(chunk, &text).await {
                Ok(stored) => stored_chunks.push(stored),
                Err(e) => {
                    tracing::error!("Failed to store chunk: {}", e);
                }
            }
        }

        Ok(stored_chunks)
    }
}
