//! Memoria Persistente — almacén SQLite de Memcons, Hectores y Atinks.
//! Sobrevive entre sesiones y permite recuperar contexto histórico.
//! Incluye FTS5 para búsqueda de texto completo.
#![allow(dead_code)]

use rusqlite::{Connection, params};

#[derive(Debug, Clone)]
pub struct MemoryItem {
    pub memcon_id: u32,
    pub text_fragment: String,
    pub domain: u8,
    pub frequency: u32,
    pub complexity: f32,
    pub last_accessed: u64,
}

#[derive(Debug)]
pub struct MemoryStats {
    pub total_memcons: u64,
    pub total_atinks: u64,
    pub total_hectores: u64,
    pub db_size_kb: u64,
}

pub struct PersistentMemory {
    pub conn: Connection,
}

impl PersistentMemory {
    pub fn new(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
        let conn = Connection::open(path)?;
        let _ = conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;");
        conn.execute(
            "CREATE TABLE IF NOT EXISTS memcons (
                id INTEGER PRIMARY KEY,
                text_fragment TEXT NOT NULL,
                domain INTEGER NOT NULL DEFAULT 0,
                frequency INTEGER NOT NULL DEFAULT 1,
                complexity REAL NOT NULL DEFAULT 0.5,
                last_accessed INTEGER NOT NULL
            )", [],
        )?;
        conn.execute(
            "CREATE TABLE IF NOT EXISTS atinks (
                source_id INTEGER NOT NULL,
                target_id INTEGER NOT NULL,
                weight REAL NOT NULL DEFAULT 1.0,
                PRIMARY KEY (source_id, target_id)
            )", [],
        )?;
        conn.execute(
            "CREATE TABLE IF NOT EXISTS hectores (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                fingerprint BLOB NOT NULL,
                created_at INTEGER NOT NULL,
                session_text TEXT
            )", [],
        )?;
        conn.execute("CREATE INDEX IF NOT EXISTS idx_memcons_domain ON memcons(domain)", [])?;
        conn.execute("CREATE INDEX IF NOT EXISTS idx_memcons_frequency ON memcons(frequency DESC)", [])?;
        let _ = conn.execute(
            "CREATE VIRTUAL TABLE IF NOT EXISTS memcons_fts USING fts5(text_fragment, domain)", [],
        );
        Ok(Self { conn })
    }

    pub fn store_memcons(
        &self,
        items: &[(u32, &str, u8, f32)],
    ) -> Result<(), Box<dyn std::error::Error>> {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)?
            .as_secs();
        for &(id, frag, domain, complexity) in items {
            self.conn.execute(
                "INSERT INTO memcons (id, text_fragment, domain, frequency, complexity, last_accessed)
                 VALUES (?1, ?2, ?3, 1, ?4, ?5)
                 ON CONFLICT(id) DO UPDATE SET
                   frequency = frequency + 1,
                   complexity = MAX(complexity, ?4),
                   last_accessed = ?5,
                   text_fragment = ?2",
                params![id, frag, domain, complexity, now],
            )?;
            let _ = self.conn.execute(
                "DELETE FROM memcons_fts WHERE rowid = ?1", params![id],
            );
            let _ = self.conn.execute(
                "INSERT INTO memcons_fts(rowid, text_fragment, domain) VALUES (?1, ?2, ?3)",
                params![id, frag, domain],
            );
        }
        Ok(())
    }

    pub fn store_atinks(
        &self,
        atinks: &[(u32, u32, f32)],
    ) -> Result<(), Box<dyn std::error::Error>> {
        for &(src, tgt, w) in atinks {
            self.conn.execute(
                "INSERT INTO atinks (source_id, target_id, weight)
                 VALUES (?1, ?2, ?3)
                 ON CONFLICT(source_id, target_id) DO UPDATE SET weight = MAX(weight, ?3)",
                params![src, tgt, w],
            )?;
        }
        Ok(())
    }

    pub fn store_hector(
        &self,
        fingerprint: &[(u32, f32)],
        session_text: &str,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)?
            .as_secs();
        let blob: Vec<u8> = fingerprint
            .iter()
            .flat_map(|&(id, act)| {
                let mut bytes = Vec::with_capacity(8);
                bytes.extend_from_slice(&id.to_le_bytes());
                bytes.extend_from_slice(&act.to_le_bytes());
                bytes
            })
            .collect();
        self.conn.execute(
            "INSERT INTO hectores (fingerprint, created_at, session_text) VALUES (?1, ?2, ?3)",
            params![blob, now, session_text],
        )?;
        Ok(())
    }

    pub fn retrieve_by_ids(
        &self,
        ids: &[u32],
        domain: u8,
        limit: usize,
    ) -> Result<Vec<MemoryItem>, Box<dyn std::error::Error>> {
        if ids.is_empty() {
            return Ok(Vec::new());
        }
        let in_placeholders: Vec<String> = ids.iter().map(|_| "?".to_string()).collect();
        let sql = format!(
            "SELECT id, text_fragment, domain, frequency, complexity, last_accessed
             FROM memcons WHERE domain = ? AND id IN ({})
             ORDER BY frequency * complexity DESC LIMIT ?",
            in_placeholders.join(",")
        );
        let mut stmt = self.conn.prepare(&sql)?;
        let mut params: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
        params.push(Box::new(domain as i64));
        for id in ids {
            params.push(Box::new(*id as i64));
        }
        params.push(Box::new(limit as i64));
        let param_refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|p| p.as_ref()).collect();

        let rows = stmt.query_map(param_refs.as_slice(), |row| {
            Ok(MemoryItem {
                memcon_id: row.get::<_, i64>(0)? as u32,
                text_fragment: row.get(1)?,
                domain: row.get::<_, i64>(2)? as u8,
                frequency: row.get::<_, i64>(3)? as u32,
                complexity: row.get(4)?,
                last_accessed: row.get::<_, i64>(5)? as u64,
            })
        })?;

        let mut items = Vec::new();
        for row in rows {
            items.push(row?);
        }
        Ok(items)
    }

    pub fn retrieve_fts(
        &self,
        question: &str,
        domain: u8,
        limit: usize,
    ) -> Result<Vec<String>, Box<dyn std::error::Error>> {
        let keywords: Vec<String> = question.split_whitespace()
            .map(|w| w.trim_matches(|c: char| !c.is_alphanumeric()).to_string())
            .filter(|w| w.len() > 2 && w != "what" && w != "where" && w != "when"
                    && w != "which" && w != "does" && w != "have" && w != "that"
                    && w != "this" && w != "with" && w != "from" && w != "about"
                    && w != "que" && w != "los" && w != "las" && w != "del"
                    && w != "por" && w != "con" && w != "una" && w != "para"
                    && w != "the" && w != "and")
            .collect();

        if keywords.is_empty() {
            return Ok(Vec::new());
        }

        let fts_query = keywords.iter()
            .map(|kw| format!("{}*", kw))
            .collect::<Vec<_>>()
            .join(" AND ");

        let domain_filter = if domain == 255 {
            String::new()
        } else {
            format!("AND domain = {}", domain as i64)
        };

        let sql = format!(
            "SELECT text_fragment FROM memcons_fts WHERE memcons_fts MATCH ?1 {} LIMIT ?2",
            domain_filter
        );

        let mut matched = Vec::new();
        if let Ok(mut stmt) = self.conn.prepare(&sql) {
            if let Ok(rows) = stmt.query_map(params![fts_query, limit as i64], |row| {
                Ok(row.get::<_, String>(0)?)
            }) {
                for row in rows {
                    if let Ok(frag) = row {
                        matched.push(frag);
                    }
                }
            }
        }
        Ok(matched)
    }

    pub fn retrieve_context(
        &self,
        question: &str,
        domain: u8,
    ) -> Result<Vec<String>, Box<dyn std::error::Error>> {
        if let Ok(fts_results) = self.retrieve_fts(question, domain, 8) {
            if !fts_results.is_empty() {
                return Ok(fts_results);
            }
        }

        let mut matched = std::collections::HashSet::new();

        let keywords: Vec<String> = question.split_whitespace()
            .map(|w| w.trim_matches(|c: char| !c.is_alphanumeric()).to_string())
            .filter(|w| w.len() > 3 && w != "what" && w != "where" && w != "when"
                    && w != "which" && w != "does" && w != "have" && w != "that"
                    && w != "this" && w != "with" && w != "from" && w != "about")
            .collect();

        let domain_filter = if domain == 255 {
            String::new()
        } else {
            format!("AND domain = {}", domain as i64)
        };

        for kw in &keywords {
            let like = format!("%{}%", kw);
            let sql = format!(
                "SELECT text_fragment FROM memcons WHERE text_fragment LIKE ?1 {} AND text_fragment GLOB '* *' AND text_fragment NOT LIKE '#%' ORDER BY frequency DESC LIMIT 5",
                domain_filter
            );
            if let Ok(mut stmt) = self.conn.prepare(&sql) {
                if let Ok(rows) = stmt.query_map(rusqlite::params![like], |row| {
                    let frag: String = row.get(0)?;
                    Ok(frag)
                }) {
                    for row in rows { if let Ok(f) = row { matched.insert(f); } }
                }
            }
        }

        let sql = format!(
            "SELECT text_fragment FROM memcons WHERE text_fragment GLOB '* *' AND length(text_fragment) > 5 {} ORDER BY frequency * complexity DESC LIMIT 5",
            domain_filter
        );
        if let Ok(mut stmt) = self.conn.prepare(&sql) {
            if let Ok(rows) = stmt.query_map([], |row| {
                let frag: String = row.get(0)?;
                Ok(frag)
            }) {
                for row in rows { if let Ok(f) = row { matched.insert(f); } }
            }
        }

        let mut result: Vec<String> = matched.into_iter().collect();
        result.truncate(8);
        Ok(result)
    }

    pub fn decay(&self, factor: f32) -> Result<(), Box<dyn std::error::Error>> {
        self.conn.execute(
            "UPDATE memcons SET frequency = MAX(1, ROUND(frequency * ?1))",
            params![factor],
        )?;
        Ok(())
    }

    pub fn prune(&self, min_frequency: u32) -> Result<(), Box<dyn std::error::Error>> {
        self.conn.execute("DELETE FROM memcons WHERE frequency < ?1", params![min_frequency])?;
        let _ = self.conn.execute(
            "DELETE FROM memcons_fts WHERE rowid NOT IN (SELECT id FROM memcons)",
            [],
        );
        self.conn.execute(
            "DELETE FROM atinks WHERE source_id NOT IN (SELECT id FROM memcons) OR target_id NOT IN (SELECT id FROM memcons)",
            [],
        )?;
        Ok(())
    }

    pub fn stats(&self) -> Result<MemoryStats, Box<dyn std::error::Error>> {
        let total_memcons: u64 = self.conn.query_row("SELECT COUNT(*) FROM memcons", [], |r| r.get(0))?;
        let total_atinks: u64 = self.conn.query_row("SELECT COUNT(*) FROM atinks", [], |r| r.get(0))?;
        let total_hectores: u64 = self.conn.query_row("SELECT COUNT(*) FROM hectores", [], |r| r.get(0))?;
        let db_size_kb: u64 = self.conn.path()
            .and_then(|p| std::fs::metadata(p).ok())
            .map(|m| m.len() / 1024)
            .unwrap_or(0);
        Ok(MemoryStats { total_memcons, total_atinks, total_hectores, db_size_kb })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn store_and_retrieve_memcon() {
        let pm = PersistentMemory::new(":memory:").unwrap();
        pm.store_memcons(&[(42, "test concept", 0, 0.8)]).unwrap();
        let items = pm.retrieve_by_ids(&[42], 0, 10).unwrap();
        assert_eq!(items.len(), 1);
        assert_eq!(items[0].memcon_id, 42);
    }

    #[test]
    fn store_twice_increments_frequency() {
        let pm = PersistentMemory::new(":memory:").unwrap();
        pm.store_memcons(&[(42, "concept", 0, 0.8)]).unwrap();
        pm.store_memcons(&[(42, "concept", 0, 0.9)]).unwrap();
        let items = pm.retrieve_by_ids(&[42], 0, 10).unwrap();
        assert_eq!(items[0].frequency, 2);
        assert_eq!(items[0].complexity, 0.9);
    }

    #[test]
    fn retrieve_by_domain() {
        let pm = PersistentMemory::new(":memory:").unwrap();
        pm.store_memcons(&[(1, "lang text", 0, 0.5)]).unwrap();
        pm.store_memcons(&[(2, "code text", 1, 0.5)]).unwrap();
        let items = pm.retrieve_by_ids(&[1, 2], 0, 10).unwrap();
        assert_eq!(items.len(), 1);
    }

    #[test]
    fn store_and_find_hector() {
        let pm = PersistentMemory::new(":memory:").unwrap();
        let fp = vec![(1u32, 1.0f32), (2u32, 0.8f32)];
        pm.store_hector(&fp, "test").unwrap();
        let results = pm.retrieve_context("test", 0).unwrap();
        assert!(results.is_empty());
    }

    #[test]
    fn prune_works() {
        let pm = PersistentMemory::new(":memory:").unwrap();
        pm.store_memcons(&[(1, "freq", 0, 0.5)]).unwrap();
        pm.store_memcons(&[(1, "freq", 0, 0.5)]).unwrap();
        pm.store_memcons(&[(2, "rare", 0, 0.5)]).unwrap();
        pm.prune(2).unwrap();
        assert_eq!(pm.stats().unwrap().total_memcons, 1);
    }

    #[test]
    fn fts_retrieve_finds_text() {
        let pm = PersistentMemory::new(":memory:").unwrap();
        pm.store_memcons(&[(100, "usamos WAL mode en SQLite para persistencia rapida", 0, 0.8)]).unwrap();
        pm.store_memcons(&[(101, "los memcons son clusters estables de neuronas", 0, 0.7)]).unwrap();
        let results = pm.retrieve_fts("SQLite persistencia", 0, 5).unwrap();
        assert!(!results.is_empty());
    }

    #[test]
    fn retrieve_context_falls_back_to_like() {
        let pm = PersistentMemory::new(":memory:").unwrap();
        pm.store_memcons(&[(200, "el perro come pan tostado", 0, 0.5)]).unwrap();
        let results = pm.retrieve_context("perro", 0).unwrap();
        assert!(!results.is_empty());
    }
}
