mod core;
mod fact;
mod format;
mod import_export;
mod persist;
mod query;
mod ranking;
mod types;

pub use import_export::{parse_import_data, ImportMerge, ImportResult, SimpleFactEntry};
pub use types::*;

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::memory_boundary::FactPrivacy;
    use crate::core::memory_policy::MemoryPolicy;
    use chrono::Utc;

    fn default_policy() -> MemoryPolicy {
        MemoryPolicy::default()
    }

    #[test]
    fn remember_and_recall() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test-project");
        k.remember(
            "architecture",
            "auth",
            "JWT RS256",
            "session-1",
            0.9,
            &policy,
        );
        k.remember("api", "rate-limit", "100/min", "session-1", 0.8, &policy);

        let results = k.recall("auth");
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].value, "JWT RS256");

        let results = k.recall("api rate");
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].key, "rate-limit");
    }

    #[test]
    fn upsert_existing_fact() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "db", "PostgreSQL", "s1", 0.7, &policy);
        k.remember(
            "arch",
            "db",
            "PostgreSQL 16 with pgvector",
            "s2",
            0.95,
            &policy,
        );

        let current: Vec<_> = k.facts.iter().filter(|f| f.is_current()).collect();
        assert_eq!(current.len(), 1);
        assert_eq!(current[0].value, "PostgreSQL 16 with pgvector");
    }

    #[test]
    fn contradiction_detection() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "db", "PostgreSQL", "s1", 0.95, &policy);
        k.facts[0].confirmation_count = 3;

        let contradiction = k.check_contradiction("arch", "db", "MySQL", &policy);
        assert!(contradiction.is_some());
        let c = contradiction.unwrap();
        assert_eq!(c.severity, ContradictionSeverity::High);
    }

    #[test]
    fn temporal_validity() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "db", "PostgreSQL", "s1", 0.95, &policy);
        k.facts[0].confirmation_count = 3;

        k.remember("arch", "db", "MySQL", "s2", 0.9, &policy);

        let current: Vec<_> = k.facts.iter().filter(|f| f.is_current()).collect();
        assert_eq!(current.len(), 1);
        assert_eq!(current[0].value, "MySQL");

        let all_db: Vec<_> = k.facts.iter().filter(|f| f.key == "db").collect();
        assert_eq!(all_db.len(), 2);
    }

    #[test]
    fn confirmation_count() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "db", "PostgreSQL", "s1", 0.9, &policy);
        assert_eq!(k.facts[0].confirmation_count, 1);

        k.remember("arch", "db", "PostgreSQL", "s2", 0.9, &policy);
        assert_eq!(k.facts[0].confirmation_count, 2);
    }

    #[test]
    fn remove_fact() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "db", "PostgreSQL", "s1", 0.9, &policy);
        assert!(k.remove_fact("arch", "db"));
        assert!(k.facts.is_empty());
        assert!(!k.remove_fact("arch", "db"));
    }

    #[test]
    fn list_rooms() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("architecture", "auth", "JWT", "s1", 0.9, &policy);
        k.remember("architecture", "db", "PG", "s1", 0.9, &policy);
        k.remember("deploy", "host", "AWS", "s1", 0.8, &policy);

        let rooms = k.list_rooms();
        assert_eq!(rooms.len(), 2);
    }

    #[test]
    fn aaak_format() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("architecture", "auth", "JWT RS256", "s1", 0.95, &policy);
        k.remember("architecture", "db", "PostgreSQL", "s1", 0.7, &policy);

        let aaak = k.format_aaak();
        assert!(aaak.contains("ARCHITECTURE:"));
        assert!(aaak.contains("auth=JWT RS256"));
    }

    #[test]
    fn consolidate_history() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.consolidate(
            "Migrated from REST to GraphQL",
            vec!["s1".into(), "s2".into()],
            &policy,
        );
        assert_eq!(k.history.len(), 1);
        assert_eq!(k.history[0].from_sessions.len(), 2);
    }

    #[test]
    fn format_summary_output() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("architecture", "auth", "JWT RS256", "s1", 0.9, &policy);
        k.add_pattern(
            "naming",
            "snake_case for functions",
            vec!["get_user()".into()],
            "s1",
            &policy,
        );
        let summary = k.format_summary();
        assert!(summary.contains("PROJECT KNOWLEDGE:"));
        assert!(summary.contains("auth: JWT RS256"));
        assert!(summary.contains("PROJECT PATTERNS:"));
    }

    #[test]
    fn temporal_recall_at_time() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "db", "PostgreSQL", "s1", 0.95, &policy);
        k.facts[0].confirmation_count = 3;

        let before_change = Utc::now();
        std::thread::sleep(std::time::Duration::from_millis(10));

        k.remember("arch", "db", "MySQL", "s2", 0.9, &policy);

        let results = k.recall_at_time("db", before_change);
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].value, "PostgreSQL");

        let results_now = k.recall_at_time("db", Utc::now());
        assert_eq!(results_now.len(), 1);
        assert_eq!(results_now[0].value, "MySQL");
    }

    #[test]
    fn timeline_shows_history() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "db", "PostgreSQL", "s1", 0.95, &policy);
        k.facts[0].confirmation_count = 3;
        k.remember("arch", "db", "MySQL", "s2", 0.9, &policy);

        let timeline = k.timeline("arch");
        assert_eq!(timeline.len(), 2);
        assert!(!timeline[0].is_current());
        assert!(timeline[1].is_current());
    }

    #[test]
    fn wakeup_format() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "auth", "JWT", "s1", 0.95, &policy);
        k.remember("arch", "db", "PG", "s1", 0.8, &policy);

        let wakeup = k.format_wakeup();
        assert!(wakeup.contains("FACTS:"));
        assert!(wakeup.contains("arch/auth=JWT"));
        assert!(wakeup.contains("arch/db=PG"));
    }

    #[test]
    fn salience_prioritizes_decisions_over_findings_at_similar_confidence() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("finding", "f1", "some thing", "s1", 0.9, &policy);
        k.remember("decision", "d1", "important", "s1", 0.85, &policy);

        let wakeup = k.format_wakeup();
        let items = wakeup
            .strip_prefix("FACTS:")
            .unwrap_or(&wakeup)
            .split('|')
            .collect::<Vec<_>>();
        assert!(
            items
                .first()
                .is_some_and(|s| s.contains("decision/d1=important")),
            "expected decision first in wakeup: {wakeup}"
        );
    }

    #[test]
    fn low_confidence_contradiction() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "db", "PostgreSQL", "s1", 0.4, &policy);

        let c = k.check_contradiction("arch", "db", "MySQL", &policy);
        assert!(c.is_some());
        assert_eq!(c.unwrap().severity, ContradictionSeverity::Low);
    }

    #[test]
    fn no_contradiction_for_same_value() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "db", "PostgreSQL", "s1", 0.95, &policy);

        let c = k.check_contradiction("arch", "db", "PostgreSQL", &policy);
        assert!(c.is_none());
    }

    #[test]
    fn no_contradiction_for_similar_values() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember(
            "arch",
            "db",
            "PostgreSQL 16 production database server",
            "s1",
            0.95,
            &policy,
        );

        let c = k.check_contradiction(
            "arch",
            "db",
            "PostgreSQL 16 production database server config",
            &policy,
        );
        assert!(c.is_none());
    }

    #[test]
    fn import_skip_existing() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "db", "PostgreSQL", "s1", 0.95, &policy);

        let incoming = vec![KnowledgeFact {
            category: "arch".into(),
            key: "db".into(),
            value: "MySQL".into(),
            source_session: "import".into(),
            confidence: 0.8,
            created_at: Utc::now(),
            last_confirmed: Utc::now(),
            retrieval_count: 0,
            last_retrieved: None,
            valid_from: Some(Utc::now()),
            valid_until: None,
            supersedes: None,
            confirmation_count: 1,
            feedback_up: 0,
            feedback_down: 0,
            last_feedback: None,
            privacy: FactPrivacy::default(),
            imported_from: None,
            archetype: KnowledgeArchetype::default(),
            fidelity: None,
        }];

        let result = k.import_facts(incoming, ImportMerge::SkipExisting, "imp-1", &policy);
        assert_eq!(result.skipped, 1);
        assert_eq!(result.added, 0);
        assert_eq!(k.facts.iter().filter(|f| f.is_current()).count(), 1);
    }

    #[test]
    fn import_replace_existing() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "db", "PostgreSQL", "s1", 0.95, &policy);

        let incoming = vec![KnowledgeFact {
            category: "arch".into(),
            key: "db".into(),
            value: "MySQL".into(),
            source_session: "import".into(),
            confidence: 0.8,
            created_at: Utc::now(),
            last_confirmed: Utc::now(),
            retrieval_count: 0,
            last_retrieved: None,
            valid_from: Some(Utc::now()),
            valid_until: None,
            supersedes: None,
            confirmation_count: 1,
            feedback_up: 0,
            feedback_down: 0,
            last_feedback: None,
            privacy: FactPrivacy::default(),
            imported_from: None,
            archetype: KnowledgeArchetype::default(),
            fidelity: None,
        }];

        let result = k.import_facts(incoming, ImportMerge::Replace, "imp-1", &policy);
        assert_eq!(result.replaced, 1);
        let current: Vec<_> = k.facts.iter().filter(|f| f.is_current()).collect();
        assert_eq!(current.len(), 1);
        assert_eq!(current[0].value, "MySQL");
    }

    #[test]
    fn import_adds_new_facts() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "db", "PostgreSQL", "s1", 0.95, &policy);

        let incoming = vec![KnowledgeFact {
            category: "security".into(),
            key: "auth".into(),
            value: "JWT".into(),
            source_session: "import".into(),
            confidence: 0.9,
            created_at: Utc::now(),
            last_confirmed: Utc::now(),
            retrieval_count: 0,
            last_retrieved: None,
            valid_from: Some(Utc::now()),
            valid_until: None,
            supersedes: None,
            confirmation_count: 1,
            feedback_up: 0,
            feedback_down: 0,
            last_feedback: None,
            privacy: FactPrivacy::default(),
            imported_from: None,
            archetype: KnowledgeArchetype::default(),
            fidelity: None,
        }];

        let result = k.import_facts(incoming, ImportMerge::SkipExisting, "imp-1", &policy);
        assert_eq!(result.added, 1);
        assert_eq!(k.facts.iter().filter(|f| f.is_current()).count(), 2);
    }

    #[test]
    fn parse_simple_json_array() {
        let data = r#"[
            {"category": "arch", "key": "db", "value": "PostgreSQL"},
            {"category": "security", "key": "auth", "value": "JWT", "confidence": 0.9}
        ]"#;
        let facts = parse_import_data(data).unwrap();
        assert_eq!(facts.len(), 2);
        assert_eq!(facts[0].category, "arch");
        assert_eq!(facts[1].confidence, 0.9);
    }

    #[test]
    fn parse_jsonl_format() {
        let data = "{\"category\":\"arch\",\"key\":\"db\",\"value\":\"PG\"}\n\
                    {\"category\":\"security\",\"key\":\"auth\",\"value\":\"JWT\"}";
        let facts = parse_import_data(data).unwrap();
        assert_eq!(facts.len(), 2);
    }

    #[test]
    fn export_simple_only_current() {
        let policy = default_policy();
        let mut k = ProjectKnowledge::new("/tmp/test");
        k.remember("arch", "db", "PostgreSQL", "s1", 0.95, &policy);
        k.remember("arch", "db", "MySQL", "s2", 0.9, &policy);

        let exported = k.export_simple();
        assert_eq!(exported.len(), 1);
        assert_eq!(exported[0].value, "MySQL");
    }

    #[test]
    fn import_merge_parse() {
        assert_eq!(ImportMerge::parse("replace"), Some(ImportMerge::Replace));
        assert_eq!(ImportMerge::parse("append"), Some(ImportMerge::Append));
        assert_eq!(
            ImportMerge::parse("skip-existing"),
            Some(ImportMerge::SkipExisting)
        );
        assert_eq!(
            ImportMerge::parse("skip_existing"),
            Some(ImportMerge::SkipExisting)
        );
        assert_eq!(ImportMerge::parse("skip"), Some(ImportMerge::SkipExisting));
        assert!(ImportMerge::parse("invalid").is_none());
    }
}
