use anyhow::Result;
use rusqlite::params;

use super::{current_state, CurrentStateRequest};
use support::*;

mod review_regressions;
mod support;

#[test]
fn returns_current_answer_when_state_key_has_single_active_current() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory(
        &conn,
        2,
        "Deploy target",
        "Use production.",
        "active",
        10,
        None,
        None,
    )?;
    set_current_memory(&conn, 2)?;

    let result = current_state(&conn, &request())?;

    assert_eq!(result.status, "current");
    assert_eq!(result.current.as_ref().map(|memory| memory.id), Some(2));
    assert!(result.conflicts.is_empty());
    Ok(())
}

#[test]
fn shows_superseded_history_and_edge_evidence_for_current_answer() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory(
        &conn,
        1,
        "Old deploy target",
        "Use staging.",
        "stale",
        10,
        Some(100),
        Some(200),
    )?;
    insert_current_state_memory(
        &conn,
        2,
        "Deploy target",
        "Use production.",
        "active",
        10,
        Some(200),
        None,
    )?;
    set_current_memory(&conn, 2)?;
    conn.execute(
        "INSERT INTO memory_edges
         (edge_type, from_memory_id, to_memory_id, state_key_id, evidence_event_ids,
          reason, created_at_epoch)
         VALUES ('supersedes', 1, 2, 10, '[7,8]', 'new deploy decision', 300)",
        [],
    )?;

    let result = current_state(&conn, &request())?;

    assert_eq!(result.status, "current");
    assert_eq!(result.history.len(), 1);
    assert_eq!(result.history[0].id, 1);
    assert_eq!(result.history[0].relation.as_deref(), Some("supersedes"));
    assert_eq!(result.history[0].evidence_event_ids, vec![7, 8]);
    assert_eq!(result.why[0].reason.as_deref(), Some("new deploy decision"));
    Ok(())
}

#[test]
fn history_preserves_edge_for_legacy_superseded_row_without_state_key() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory(
        &conn,
        1,
        "Legacy deploy target",
        "Use staging.",
        "stale",
        10,
        Some(100),
        Some(200),
    )?;
    conn.execute("UPDATE memories SET state_key_id = NULL WHERE id = 1", [])?;
    insert_current_state_memory(
        &conn,
        2,
        "Deploy target",
        "Use production.",
        "active",
        10,
        Some(200),
        None,
    )?;
    set_current_memory(&conn, 2)?;
    conn.execute(
        "INSERT INTO memory_edges
         (edge_type, from_memory_id, to_memory_id, state_key_id, evidence_event_ids,
          reason, created_at_epoch)
         VALUES ('supersedes', 1, 2, 10, '[9]', 'legacy replacement', 300)",
        [],
    )?;

    let result = current_state(&conn, &request())?;

    assert_eq!(result.status, "current");
    assert_eq!(result.history.len(), 1);
    assert_eq!(result.history[0].id, 1);
    assert_eq!(result.history[0].relation.as_deref(), Some("supersedes"));
    assert_eq!(result.history[0].evidence_event_ids, vec![9]);
    assert_eq!(
        result.history[0].reason.as_deref(),
        Some("legacy replacement")
    );
    Ok(())
}

#[test]
fn unresolved_active_conflict_does_not_silently_choose_current_pointer() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory(
        &conn,
        2,
        "Deploy target",
        "Use production.",
        "active",
        10,
        None,
        None,
    )?;
    insert_current_state_memory(
        &conn,
        3,
        "Deploy target conflict",
        "Use staging.",
        "active",
        10,
        None,
        None,
    )?;
    set_current_memory(&conn, 2)?;
    conn.execute(
        "INSERT INTO memory_edges
         (edge_type, from_memory_id, to_memory_id, state_key_id, reason, created_at_epoch)
         VALUES ('conflicts', 3, 2, 10, 'operator conflict', 300)",
        [],
    )?;

    let result = current_state(&conn, &request())?;

    assert_eq!(result.status, "unresolved_conflict");
    assert_eq!(result.current.as_ref().map(|memory| memory.id), Some(2));
    assert_eq!(result.conflicts.len(), 1);
    assert_eq!(result.conflicts[0].id, 3);
    Ok(())
}

#[test]
fn as_of_time_uses_memory_validity_window() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory(
        &conn,
        1,
        "Old deploy target",
        "Use staging.",
        "stale",
        10,
        Some(100),
        Some(200),
    )?;
    insert_current_state_memory(
        &conn,
        2,
        "Deploy target",
        "Use production.",
        "active",
        10,
        Some(200),
        None,
    )?;
    set_current_memory(&conn, 2)?;

    let result = current_state(
        &conn,
        &CurrentStateRequest {
            as_of_epoch: Some(150),
            ..request()
        },
    )?;

    assert_eq!(result.status, "current");
    assert_eq!(result.current.as_ref().map(|memory| memory.id), Some(1));
    assert_eq!(
        result.current.as_ref().map(|memory| memory.status.as_str()),
        Some("stale")
    );
    Ok(())
}

#[test]
fn omitted_project_defaults_to_current_repo_scope() -> Result<()> {
    let conn = current_state_test_conn()?;
    let cwd = std::env::current_dir()?;
    let current_project = crate::db::project_from_cwd(cwd.to_string_lossy().as_ref());
    insert_state_key_for(&conn, 10, &current_project, None)?;
    insert_state_key_for(&conn, 11, "/other/repo", None)?;
    insert_current_state_memory_at(
        &conn,
        2,
        &current_project,
        "Current repo deploy target",
        "Use production.",
        "active",
        10,
        100,
        None,
        None,
    )?;
    insert_current_state_memory_at(
        &conn,
        3,
        "/other/repo",
        "Other repo deploy target",
        "Use staging.",
        "active",
        11,
        100,
        None,
        None,
    )?;
    conn.execute(
        "UPDATE memory_state_keys SET current_memory_id = 2 WHERE id = 10",
        [],
    )?;
    conn.execute(
        "UPDATE memory_state_keys SET current_memory_id = 3 WHERE id = 11",
        [],
    )?;

    let result = current_state(
        &conn,
        &CurrentStateRequest {
            project: None,
            ..request()
        },
    )?;

    assert_eq!(result.status, "current");
    assert_eq!(result.current.as_ref().map(|memory| memory.id), Some(2));
    assert!(result.matches.is_empty());
    Ok(())
}

#[test]
fn as_of_time_excludes_memories_created_after_cutoff_when_valid_from_is_null() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory_at(
        &conn,
        1,
        "/repo",
        "Old deploy target",
        "Use staging.",
        "stale",
        10,
        100,
        None,
        Some(200),
    )?;
    insert_current_state_memory_at(
        &conn,
        2,
        "/repo",
        "Future deploy target",
        "Use production.",
        "active",
        10,
        300,
        None,
        None,
    )?;
    set_current_memory(&conn, 2)?;

    let result = current_state(
        &conn,
        &CurrentStateRequest {
            as_of_epoch: Some(150),
            ..request()
        },
    )?;

    assert_eq!(result.status, "current");
    assert_eq!(result.current.as_ref().map(|memory| memory.id), Some(1));
    Ok(())
}

#[test]
fn as_of_time_includes_archived_memories_inside_validity_window() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory_at(
        &conn,
        1,
        "/repo",
        "Archived deploy target",
        "Use staging.",
        "archived",
        10,
        100,
        Some(100),
        Some(200),
    )?;
    insert_current_state_memory_at(
        &conn,
        2,
        "/repo",
        "Deploy target",
        "Use production.",
        "active",
        10,
        200,
        Some(200),
        None,
    )?;
    set_current_memory(&conn, 2)?;

    let result = current_state(
        &conn,
        &CurrentStateRequest {
            as_of_epoch: Some(150),
            ..request()
        },
    )?;

    assert_eq!(result.status, "current");
    assert_eq!(result.current.as_ref().map(|memory| memory.id), Some(1));
    assert_eq!(
        result.current.as_ref().map(|memory| memory.status.as_str()),
        Some("archived")
    );
    Ok(())
}

#[test]
fn as_of_time_excludes_archived_memories_without_valid_to_epoch() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory_at(
        &conn,
        1,
        "/repo",
        "Archived deploy target",
        "Use staging.",
        "archived",
        10,
        100,
        Some(100),
        None,
    )?;
    insert_current_state_memory_at(
        &conn,
        2,
        "/repo",
        "Future deploy target",
        "Use production.",
        "active",
        10,
        300,
        Some(300),
        None,
    )?;
    set_current_memory(&conn, 2)?;

    let result = current_state(
        &conn,
        &CurrentStateRequest {
            as_of_epoch: Some(150),
            ..request()
        },
    )?;

    assert_eq!(result.status, "no_current");
    assert!(result.current.is_none());
    Ok(())
}

#[test]
fn current_answer_facts_exclude_expired_and_future_active_facts() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory(
        &conn,
        2,
        "Deploy target",
        "Use production.",
        "active",
        10,
        None,
        None,
    )?;
    set_current_memory(&conn, 2)?;
    let now = chrono::Utc::now().timestamp();
    for (id, object, valid_from, valid_to) in [
        (1_i64, "production", now - 100, Some(now + 100)),
        (2_i64, "expired", now - 200, Some(now - 100)),
        (3_i64, "future", now + 100, None),
    ] {
        conn.execute(
            "INSERT INTO memory_facts
             (id, project, subject, predicate, object, valid_from_epoch,
              valid_to_epoch, learned_at_epoch, source_memory_id, source_event_ids,
              confidence, status, created_at_epoch, updated_at_epoch)
             VALUES (?1, '/repo', 'deploy-target', 'affects_project', ?2, ?3,
                     ?4, ?3, 2, '[]', 0.9, 'active', ?3, ?3)",
            params![id, object, valid_from, valid_to],
        )?;
    }

    let result = current_state(&conn, &request())?;

    assert_eq!(
        result
            .facts
            .iter()
            .map(|fact| fact.object.as_str())
            .collect::<Vec<_>>(),
        vec!["production"]
    );
    Ok(())
}

#[test]
fn as_of_time_excludes_expired_memory_before_cleanup_sets_valid_to() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory_with_expiry_at(
        &conn,
        2,
        "/repo",
        "Temporary deploy target",
        "Use production until expiry.",
        "active",
        10,
        100,
        Some(200),
        Some(100),
        None,
    )?;
    set_current_memory(&conn, 2)?;

    let result = current_state(
        &conn,
        &CurrentStateRequest {
            as_of_epoch: Some(250),
            ..request()
        },
    )?;

    assert_eq!(result.status, "no_current");
    assert!(result.current.is_none());
    Ok(())
}

#[test]
fn why_includes_derived_from_provenance_for_current_answer() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory(
        &conn,
        2,
        "Deploy target",
        "Use production.",
        "active",
        10,
        None,
        None,
    )?;
    set_current_memory(&conn, 2)?;
    conn.execute(
        "INSERT INTO memory_candidates
         (id, project_id, scope, memory_type, topic_key, text, evidence_event_ids,
          confidence, risk_class, review_status, created_at_epoch, updated_at_epoch)
         VALUES (20, NULL, 'project', 'decision', 'deploy-target', 'Use production.',
                 '[11,12]', 0.9, 'low', 'approved', 90, 90)",
        [],
    )?;
    conn.execute(
        "INSERT INTO memory_operation_log
         (id, operation, planner_version, actor, source, owner_scope, owner_key,
          memory_type, state_key, source_candidate_id, result_memory_id, created_at_epoch)
         VALUES (30, 'add', 'test', 'test', 'test', 'repo', '/repo',
                 'decision', 'deploy-target', 20, 2, 95)",
        [],
    )?;
    conn.execute(
        "INSERT INTO memory_edges
         (edge_type, from_memory_id, to_memory_id, state_key_id, source_candidate_id,
          evidence_event_ids, source_operation_id, reason, created_at_epoch)
         VALUES ('derived_from', NULL, 2, NULL, 20, '[11,12]', 30,
                 'candidate promoted', 100)",
        [],
    )?;

    let result = current_state(&conn, &request())?;

    assert_eq!(result.why.len(), 1);
    assert_eq!(result.why[0].edge_type, "derived_from");
    assert_eq!(result.why[0].to_memory_id, Some(2));
    assert_eq!(result.why[0].evidence_event_ids, vec![11, 12]);
    assert_eq!(result.why[0].source_candidate_id, Some(20));
    assert_eq!(result.why[0].source_operation_id, Some(30));
    Ok(())
}

#[test]
fn edge_details_scope_to_resolved_state_key() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    conn.execute(
        "INSERT INTO memory_state_keys
         (id, owner_scope, owner_key, memory_type, state_key, state_label,
          state_status, current_memory_id, created_at_epoch, updated_at_epoch)
         VALUES (11, 'repo', '/repo', 'decision', 'other-deploy-target',
                 'other deploy target', 'active', NULL, 1700000000, 1700000010)",
        [],
    )?;
    insert_current_state_memory(
        &conn,
        1,
        "Old deploy target",
        "Use staging.",
        "stale",
        10,
        Some(100),
        Some(200),
    )?;
    insert_current_state_memory(
        &conn,
        2,
        "Deploy target",
        "Use production.",
        "active",
        10,
        Some(200),
        None,
    )?;
    insert_current_state_memory(
        &conn,
        3,
        "Other deploy target",
        "Use canary.",
        "stale",
        11,
        Some(100),
        Some(200),
    )?;
    set_current_memory(&conn, 2)?;
    conn.execute(
        "INSERT INTO memory_edges
         (edge_type, from_memory_id, to_memory_id, state_key_id, evidence_event_ids,
          reason, created_at_epoch)
         VALUES ('supersedes', 1, 2, 10, '[7]', 'same state', 300)",
        [],
    )?;
    conn.execute(
        "INSERT INTO memory_edges
         (edge_type, from_memory_id, to_memory_id, state_key_id, evidence_event_ids,
          reason, created_at_epoch)
         VALUES ('merged_into', 3, 2, 11, '[8]', 'different state', 310)",
        [],
    )?;

    let result = current_state(&conn, &request())?;

    assert_eq!(
        result
            .history
            .iter()
            .map(|memory| memory.id)
            .collect::<Vec<_>>(),
        vec![1]
    );
    assert_eq!(
        result
            .why
            .iter()
            .map(|edge| edge.reason.as_deref())
            .collect::<Vec<_>>(),
        vec![Some("same state")]
    );
    Ok(())
}

#[test]
fn as_of_why_excludes_edges_created_after_cutoff() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory_at(
        &conn,
        1,
        "/repo",
        "Old deploy target",
        "Use staging.",
        "stale",
        10,
        100,
        Some(100),
        Some(200),
    )?;
    insert_current_state_memory_at(
        &conn,
        2,
        "/repo",
        "Deploy target",
        "Use production.",
        "active",
        10,
        200,
        Some(200),
        None,
    )?;
    set_current_memory(&conn, 2)?;
    conn.execute(
        "INSERT INTO memory_edges
         (edge_type, from_memory_id, to_memory_id, state_key_id, evidence_event_ids,
          reason, created_at_epoch)
         VALUES ('derived_from', NULL, 1, 10, '[1]', 'original candidate', 120)",
        [],
    )?;
    conn.execute(
        "INSERT INTO memory_edges
         (edge_type, from_memory_id, to_memory_id, state_key_id, evidence_event_ids,
          reason, created_at_epoch)
         VALUES ('conflicts', 1, 2, 10, '[2]', 'future conflict', 180)",
        [],
    )?;

    let result = current_state(
        &conn,
        &CurrentStateRequest {
            as_of_epoch: Some(150),
            ..request()
        },
    )?;

    assert_eq!(result.current.as_ref().map(|memory| memory.id), Some(1));
    assert_eq!(
        result
            .why
            .iter()
            .map(|edge| edge.edge_type.as_str())
            .collect::<Vec<_>>(),
        vec!["derived_from"]
    );
    assert_eq!(result.why[0].evidence_event_ids, vec![1]);
    Ok(())
}

#[test]
fn as_of_history_excludes_edges_created_after_cutoff() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory_at(
        &conn,
        2,
        "/repo",
        "Deploy target",
        "Use production.",
        "active",
        10,
        100,
        Some(100),
        None,
    )?;
    insert_current_state_memory_at(
        &conn,
        3,
        "/repo",
        "Future merged target",
        "Use canary.",
        "stale",
        10,
        130,
        Some(130),
        Some(150),
    )?;
    set_current_memory(&conn, 2)?;
    conn.execute(
        "INSERT INTO memory_edges
         (edge_type, from_memory_id, to_memory_id, state_key_id, evidence_event_ids,
          reason, created_at_epoch)
         VALUES ('merged_into', 3, 2, 10, '[9]', 'future merge', 150)",
        [],
    )?;

    let result = current_state(
        &conn,
        &CurrentStateRequest {
            as_of_epoch: Some(120),
            ..request()
        },
    )?;

    assert_eq!(result.current.as_ref().map(|memory| memory.id), Some(2));
    assert!(result.history.is_empty());
    Ok(())
}

#[test]
fn as_of_facts_exclude_facts_learned_after_cutoff() -> Result<()> {
    let conn = current_state_test_conn()?;
    insert_state_key(&conn)?;
    insert_current_state_memory_at(
        &conn,
        2,
        "/repo",
        "Deploy target",
        "Use production.",
        "active",
        10,
        100,
        Some(100),
        None,
    )?;
    set_current_memory(&conn, 2)?;
    for (id, object, learned_at) in [(1_i64, "known", 110_i64), (2_i64, "future", 150_i64)] {
        conn.execute(
            "INSERT INTO memory_facts
             (id, project, subject, predicate, object, valid_from_epoch,
              valid_to_epoch, learned_at_epoch, source_memory_id, source_event_ids,
              confidence, status, created_at_epoch, updated_at_epoch)
             VALUES (?1, '/repo', 'deploy-target', 'affects_project', ?2, 100,
                     NULL, ?3, 2, '[]', 0.9, 'active', ?3, ?3)",
            params![id, object, learned_at],
        )?;
    }

    let result = current_state(
        &conn,
        &CurrentStateRequest {
            as_of_epoch: Some(120),
            ..request()
        },
    )?;

    assert_eq!(
        result
            .facts
            .iter()
            .map(|fact| fact.object.as_str())
            .collect::<Vec<_>>(),
        vec!["known"]
    );
    Ok(())
}
