use std::path::PathBuf;

use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use uuid::Uuid;

use second_brain_core::schema::{Conversation, Memory, MemoryType};

use crate::{Adapter, ConversationSource, IngestResult};

pub struct CursorAdapter {
    base_path: PathBuf,
    machine_id: String,
}

impl CursorAdapter {
    pub fn new(machine_id: String) -> Self {
        let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
        let base_path = PathBuf::from(home).join(".cursor");
        Self {
            base_path,
            machine_id,
        }
    }

    pub fn with_path(base_path: PathBuf, machine_id: String) -> Self {
        Self {
            base_path,
            machine_id,
        }
    }

    fn parse_conversation_file(
        &self,
        path: &PathBuf,
    ) -> Result<(Vec<Memory>, Option<DateTime<Utc>>)> {
        let content = std::fs::read_to_string(path)
            .with_context(|| format!("reading conversation file: {}", path.display()))?;

        let mut memories = Vec::new();
        let mut first_timestamp: Option<DateTime<Utc>> = None;
        let source_id = path.to_string_lossy().to_string();

        for line in content.lines() {
            if line.trim().is_empty() {
                continue;
            }

            let msg: serde_json::Value = match serde_json::from_str(line) {
                Ok(v) => v,
                Err(_) => continue,
            };

            let role = msg.get("role").and_then(|r| r.as_str()).unwrap_or("");

            if first_timestamp.is_none() {
                if let Some(ts) = msg.get("timestamp").and_then(|t| t.as_str()) {
                    first_timestamp = DateTime::parse_from_rfc3339(ts)
                        .map(|dt| dt.with_timezone(&Utc))
                        .ok();
                }
            }

            if role != "assistant" {
                continue;
            }

            let message = match msg.get("message") {
                Some(m) => m,
                None => continue,
            };

            let content_blocks = match message.get("content").and_then(|c| c.as_array()) {
                Some(blocks) => blocks,
                None => continue,
            };

            for block in content_blocks {
                let block_type = block.get("type").and_then(|t| t.as_str()).unwrap_or("");

                let text = match block_type {
                    "text" => block.get("text").and_then(|t| t.as_str()),
                    _ => None,
                };

                if let Some(text) = text {
                    if is_memorable(text) {
                        let memory_type = classify_memory(text);
                        let memory = Memory::new(
                            text.to_string(),
                            memory_type,
                            "cursor".to_string(),
                            source_id.clone(),
                        );
                        memories.push(memory);
                    }
                }
            }
        }

        Ok((memories, first_timestamp))
    }
}

impl Adapter for CursorAdapter {
    fn name(&self) -> &str {
        "cursor"
    }

    fn discover_conversations(&self) -> Result<Vec<ConversationSource>> {
        let projects_dir = self.base_path.join("projects");
        let mut sources = Vec::new();

        if !projects_dir.exists() {
            return Ok(sources);
        }

        let mut dirs = vec![projects_dir];
        while let Some(dir) = dirs.pop() {
            for entry in std::fs::read_dir(&dir)? {
                let entry = entry?;
                let path = entry.path();
                if path.is_dir() {
                    dirs.push(path);
                } else if path.extension().is_some_and(|e| e == "jsonl") {
                    sources.push(ConversationSource {
                        id: path.to_string_lossy().to_string(),
                        path: path.to_string_lossy().to_string(),
                        source_name: "cursor".to_string(),
                        machine_id: self.machine_id.clone(),
                    });
                }
            }
        }

        Ok(sources)
    }

    fn ingest_conversation(&self, source: &ConversationSource) -> Result<IngestResult> {
        let path = PathBuf::from(&source.path);
        let (memories, started_at) = self.parse_conversation_file(&path)?;

        let project_dir = path
            .parent()
            .and_then(|p| p.parent())
            .and_then(|p| p.parent())
            .and_then(|p| p.file_name().map(|n| n.to_string_lossy().to_string()));

        let conversation = Conversation {
            id: Uuid::new_v4(),
            source: "cursor".to_string(),
            machine_id: source.machine_id.clone(),
            started_at: started_at.unwrap_or_else(Utc::now),
            project_path: project_dir,
        };

        Ok(IngestResult {
            conversation,
            memories,
            entities: Vec::new(),
            entities_extracted: 0,
        })
    }
}

fn is_memorable(text: &str) -> bool {
    if text.len() < 100 || text.len() > 5000 {
        return false;
    }
    let code_block_count = text.matches("```").count();
    if code_block_count > 4 {
        return false;
    }
    let non_code_lines = text
        .lines()
        .filter(|l| {
            let trimmed = l.trim();
            !trimmed.is_empty()
                && !trimmed.starts_with("```")
                && !trimmed.starts_with("  ")
                && !trimmed.starts_with('\t')
        })
        .count();
    non_code_lines >= 3
}

fn classify_memory(text: &str) -> MemoryType {
    let lower = text.to_lowercase();
    let decision_markers = [
        "decided",
        "chose",
        "went with",
        "picked",
        "agreed",
        "choosing",
        "selected",
    ];
    let procedural_markers = [
        "to do this",
        "steps:",
        "step 1",
        "first,",
        "then,",
        "finally,",
        "run ",
        "install ",
        "execute",
    ];

    if decision_markers.iter().any(|m| lower.contains(m)) {
        MemoryType::Episodic
    } else if procedural_markers.iter().any(|m| lower.contains(m)) {
        MemoryType::Procedural
    } else {
        MemoryType::Semantic
    }
}
