use crate::traits::{Memory, MemoryCategory, MemoryEntry};
use async_trait::async_trait;
use chrono::Utc;
use std::path::{Path, PathBuf};

pub struct MarkdownMemory {
    root: PathBuf,
}

impl MarkdownMemory {
    pub fn new(workspace_dir: &Path) -> Self {
        Self {
            root: workspace_dir.join("memory"),
        }
    }

    fn file_for(&self, key: &str) -> PathBuf {
        let safe = key
            .chars()
            .map(|c| {
                if c.is_ascii_alphanumeric() || matches!(c, '-' | '_') {
                    c
                } else {
                    '_'
                }
            })
            .collect::<String>();
        self.root.join(format!("{safe}.md"))
    }

    fn parse_entry(path: &Path, content: &str) -> Option<MemoryEntry> {
        let key = path.file_stem()?.to_str()?.to_string();
        Some(MemoryEntry {
            id: key.clone(),
            key,
            content: content.to_string(),
            category: MemoryCategory::Conversation,
            timestamp: Utc::now().to_rfc3339(),
            session_id: None,
            score: None,
        })
    }
}

#[async_trait]
impl Memory for MarkdownMemory {
    fn name(&self) -> &str {
        "markdown"
    }

    async fn store(
        &self,
        key: &str,
        content: &str,
        _category: MemoryCategory,
        _session_id: Option<&str>,
    ) -> anyhow::Result<()> {
        tokio::fs::create_dir_all(&self.root).await?;
        tokio::fs::write(self.file_for(key), content).await?;
        Ok(())
    }

    async fn recall(
        &self,
        query: &str,
        limit: usize,
        _session_id: Option<&str>,
    ) -> anyhow::Result<Vec<MemoryEntry>> {
        let mut entries = self.list(None, None).await?;
        let q = query.to_ascii_lowercase();
        entries.retain(|e| {
            e.key.to_ascii_lowercase().contains(&q) || e.content.to_ascii_lowercase().contains(&q)
        });
        entries.truncate(limit);
        Ok(entries)
    }

    async fn get(&self, key: &str) -> anyhow::Result<Option<MemoryEntry>> {
        let path = self.file_for(key);
        if !path.exists() {
            return Ok(None);
        }
        let content = tokio::fs::read_to_string(&path).await?;
        Ok(Self::parse_entry(&path, &content))
    }

    async fn list(
        &self,
        _category: Option<&MemoryCategory>,
        _session_id: Option<&str>,
    ) -> anyhow::Result<Vec<MemoryEntry>> {
        let mut out = Vec::new();
        if !self.root.exists() {
            return Ok(out);
        }

        let mut dir = tokio::fs::read_dir(&self.root).await?;
        while let Some(entry) = dir.next_entry().await? {
            let path = entry.path();
            if path.extension().and_then(|e| e.to_str()) != Some("md") {
                continue;
            }
            let content = tokio::fs::read_to_string(&path).await?;
            if let Some(parsed) = Self::parse_entry(&path, &content) {
                out.push(parsed);
            }
        }
        Ok(out)
    }

    async fn forget(&self, key: &str) -> anyhow::Result<bool> {
        let path = self.file_for(key);
        if !path.exists() {
            return Ok(false);
        }
        tokio::fs::remove_file(path).await?;
        Ok(true)
    }

    async fn count(&self) -> anyhow::Result<usize> {
        Ok(self.list(None, None).await?.len())
    }

    async fn health_check(&self) -> bool {
        tokio::fs::create_dir_all(&self.root).await.is_ok()
    }
}
