use super::*;

/// Creates an in-memory database and applies the task_events DDL directly.
///
/// The embedded schema (version 23) does not include task_events; that table
/// is added by migration 029 which runs at production startup.  In tests the
/// migrations directory is not on the search path, so we bootstrap the table
/// inline here instead of relying on `run_migrations`.
fn test_db() -> Database {
    let db = Database::new(":memory:").expect("in-memory db");
    db.conn()
        .execute_batch(
            "CREATE TABLE IF NOT EXISTS task_events ( \
                id TEXT PRIMARY KEY, \
                task_id TEXT NOT NULL, \
                parent_task_id TEXT, \
                assigned_to TEXT, \
                event_type TEXT NOT NULL CHECK ( \
                    event_type IN ('pending','assigned','running','progress','completed','failed','cancelled','retry') \
                ), \
                summary TEXT, \
                detail_json TEXT, \
                percentage REAL, \
                retry_count INTEGER NOT NULL DEFAULT 0, \
                created_at TEXT NOT NULL DEFAULT (datetime('now')) \
            ); \
            CREATE INDEX IF NOT EXISTS idx_task_events_task_id ON task_events(task_id); \
            CREATE INDEX IF NOT EXISTS idx_task_events_parent ON task_events(parent_task_id); \
            CREATE INDEX IF NOT EXISTS idx_task_events_assigned_to ON task_events(assigned_to); \
            CREATE INDEX IF NOT EXISTS idx_task_events_created ON task_events(created_at DESC); \
            CREATE INDEX IF NOT EXISTS idx_task_events_type ON task_events(event_type);",
        )
        .expect("task_events DDL");
    db
}

fn make_event(task_id: &str, event_type: TaskLifecycleState) -> TaskEventRow {
    TaskEventRow {
        id: uuid::Uuid::new_v4().to_string(),
        task_id: task_id.to_string(),
        parent_task_id: None,
        assigned_to: None,
        event_type,
        summary: None,
        detail_json: None,
        percentage: None,
        retry_count: 0,
        created_at: String::new(), // filled by DB default
    }
}

#[test]
fn lifecycle_state_round_trips_through_serde() {
    let states = [
        TaskLifecycleState::Pending,
        TaskLifecycleState::Assigned,
        TaskLifecycleState::Running,
        TaskLifecycleState::Progress,
        TaskLifecycleState::Completed,
        TaskLifecycleState::Failed,
        TaskLifecycleState::Cancelled,
        TaskLifecycleState::Retry,
    ];
    for state in &states {
        let json = serde_json::to_string(state).expect("serialize");
        let back: TaskLifecycleState = serde_json::from_str(&json).expect("deserialize");
        assert_eq!(state, &back, "round-trip failed for {:?}", state);
    }
}

#[test]
fn lifecycle_state_display_is_lowercase() {
    assert_eq!(TaskLifecycleState::Pending.as_str(), "pending");
    assert_eq!(TaskLifecycleState::Assigned.as_str(), "assigned");
    assert_eq!(TaskLifecycleState::Running.as_str(), "running");
    assert_eq!(TaskLifecycleState::Progress.as_str(), "progress");
    assert_eq!(TaskLifecycleState::Completed.as_str(), "completed");
    assert_eq!(TaskLifecycleState::Failed.as_str(), "failed");
    assert_eq!(TaskLifecycleState::Cancelled.as_str(), "cancelled");
    assert_eq!(TaskLifecycleState::Retry.as_str(), "retry");
    // Display trait delegates to as_str
    assert_eq!(format!("{}", TaskLifecycleState::Running), "running");
}

#[test]
fn lifecycle_state_is_copy() {
    let s = TaskLifecycleState::Running;
    let s2 = s; // copy — no move
    assert_eq!(s, s2);
}

#[test]
fn insert_and_query_task_events() {
    let db = test_db();
    let event = TaskEventRow {
        id: "evt-1".to_string(),
        task_id: "task-abc".to_string(),
        parent_task_id: None,
        assigned_to: Some("agent-alpha".to_string()),
        event_type: TaskLifecycleState::Pending,
        summary: Some("Task created".to_string()),
        detail_json: Some(r#"{"source":"api"}"#.to_string()),
        percentage: None,
        retry_count: 0,
        created_at: String::new(),
    };
    insert_task_event(&db, &event).unwrap();

    let events = task_events_for_task(&db, "task-abc").unwrap();
    assert_eq!(events.len(), 1);
    assert_eq!(events[0].id, "evt-1");
    assert_eq!(events[0].task_id, "task-abc");
    assert_eq!(events[0].assigned_to.as_deref(), Some("agent-alpha"));
    assert_eq!(events[0].event_type, TaskLifecycleState::Pending);
    assert_eq!(events[0].summary.as_deref(), Some("Task created"));
}

#[test]
fn query_task_events_by_assigned_to() {
    let db = test_db();

    let mut e1 = make_event("task-1", TaskLifecycleState::Running);
    e1.id = "e1".to_string();
    e1.assigned_to = Some("agent-beta".to_string());

    let mut e2 = make_event("task-2", TaskLifecycleState::Completed);
    e2.id = "e2".to_string();
    e2.assigned_to = Some("agent-gamma".to_string());

    let mut e3 = make_event("task-3", TaskLifecycleState::Pending);
    e3.id = "e3".to_string();
    e3.assigned_to = Some("agent-beta".to_string());

    insert_task_event(&db, &e1).unwrap();
    insert_task_event(&db, &e2).unwrap();
    insert_task_event(&db, &e3).unwrap();

    let beta_events = task_events_for_agent(&db, "agent-beta").unwrap();
    assert_eq!(beta_events.len(), 2);
    assert!(
        beta_events
            .iter()
            .all(|e| e.assigned_to.as_deref() == Some("agent-beta"))
    );

    let gamma_events = task_events_for_agent(&db, "agent-gamma").unwrap();
    assert_eq!(gamma_events.len(), 1);
}

#[test]
fn latest_event_returns_most_recent() {
    let db = test_db();

    // Use distinct timestamps so ordering is deterministic.
    let mut e1 = make_event("task-x", TaskLifecycleState::Pending);
    e1.id = "ex-1".to_string();
    e1.created_at = "2026-01-01 09:00:00".to_string();

    let mut e2 = make_event("task-x", TaskLifecycleState::Running);
    e2.id = "ex-2".to_string();
    e2.created_at = "2026-01-01 09:00:01".to_string();

    insert_task_event(&db, &e1).unwrap();
    insert_task_event(&db, &e2).unwrap();

    let latest = latest_task_event(&db, "task-x").unwrap();
    assert!(latest.is_some());
    let row = latest.unwrap();
    assert_eq!(row.task_id, "task-x");
    assert_eq!(
        row.event_type,
        TaskLifecycleState::Running,
        "latest should be running (later timestamp)"
    );

    assert!(latest_task_event(&db, "nonexistent").unwrap().is_none());
}

#[test]
fn current_state_for_task_returns_terminal_state() {
    let db = test_db();

    // Use distinct timestamps to guarantee ordering: running first, completed after.
    let mut e1 = make_event("task-y", TaskLifecycleState::Running);
    e1.id = "ey-1".to_string();
    e1.created_at = "2026-01-01 10:00:00".to_string();

    let mut e2 = make_event("task-y", TaskLifecycleState::Completed);
    e2.id = "ey-2".to_string();
    e2.created_at = "2026-01-01 10:00:01".to_string();

    insert_task_event(&db, &e1).unwrap();
    insert_task_event(&db, &e2).unwrap();

    let state = current_task_state(&db, "task-y").unwrap();
    let state = state.expect("state should exist");
    assert!(
        state.is_terminal(),
        "expected terminal state, got {:?}",
        state
    );

    assert!(current_task_state(&db, "nonexistent").unwrap().is_none());
}

#[test]
fn recent_task_events_respects_limit() {
    let db = test_db();

    for i in 0..10u32 {
        let mut e = make_event(&format!("task-{}", i), TaskLifecycleState::Pending);
        e.id = format!("e-limit-{}", i);
        // Use explicit timestamps to guarantee deterministic ordering.
        e.created_at = format!("2026-01-01 12:{:02}:00", i);
        insert_task_event(&db, &e).unwrap();
    }

    let events = recent_task_events(&db, 5).unwrap();
    assert_eq!(events.len(), 5);

    let all = recent_task_events(&db, 100).unwrap();
    assert_eq!(all.len(), 10);
}

#[test]
fn retry_count_tracks_retries() {
    let db = test_db();

    let mut e1 = make_event("task-r", TaskLifecycleState::Running);
    e1.id = "er-1".to_string();
    e1.retry_count = 0;

    let mut e2 = make_event("task-r", TaskLifecycleState::Retry);
    e2.id = "er-2".to_string();
    e2.retry_count = 1;

    let mut e3 = make_event("task-r", TaskLifecycleState::Retry);
    e3.id = "er-3".to_string();
    e3.retry_count = 2;

    insert_task_event(&db, &e1).unwrap();
    insert_task_event(&db, &e2).unwrap();
    insert_task_event(&db, &e3).unwrap();

    assert_eq!(retry_count_for_task(&db, "task-r").unwrap(), 2);
    assert_eq!(retry_count_for_task(&db, "nonexistent").unwrap(), 0);
}

#[test]
fn active_task_summaries_returns_only_non_terminal() {
    let db = test_db();

    // task-active: currently running (non-terminal)
    let mut e1 = make_event("task-active", TaskLifecycleState::Pending);
    e1.id = "act-1".to_string();
    e1.created_at = "2026-01-01 11:00:00".to_string();

    let mut e2 = make_event("task-active", TaskLifecycleState::Running);
    e2.id = "act-2".to_string();
    e2.created_at = "2026-01-01 11:00:01".to_string();

    // task-done: completed (terminal — should NOT appear)
    let mut e3 = make_event("task-done", TaskLifecycleState::Running);
    e3.id = "done-1".to_string();
    e3.created_at = "2026-01-01 11:01:00".to_string();

    let mut e4 = make_event("task-done", TaskLifecycleState::Completed);
    e4.id = "done-2".to_string();
    e4.created_at = "2026-01-01 11:01:01".to_string();

    // task-failed: failed (terminal — should NOT appear)
    let mut e5 = make_event("task-failed", TaskLifecycleState::Failed);
    e5.id = "fail-1".to_string();
    e5.created_at = "2026-01-01 11:02:00".to_string();

    // task-progress: in progress (non-terminal)
    let mut e6 = make_event("task-progress", TaskLifecycleState::Progress);
    e6.id = "prog-1".to_string();
    e6.created_at = "2026-01-01 11:03:00".to_string();

    for e in [&e1, &e2, &e3, &e4, &e5, &e6] {
        insert_task_event(&db, e).unwrap();
    }

    let active = active_task_summaries(&db).unwrap();

    // Only task-active (Running) and task-progress (Progress) should appear.
    assert_eq!(
        active.len(),
        2,
        "expected 2 active tasks, got {}",
        active.len()
    );

    let task_ids: Vec<&str> = active.iter().map(|r| r.task_id.as_str()).collect();
    assert!(
        task_ids.contains(&"task-active"),
        "task-active should be active"
    );
    assert!(
        task_ids.contains(&"task-progress"),
        "task-progress should be active"
    );

    // Verify we got the latest event per task (not the pending one for task-active).
    let active_row = active.iter().find(|r| r.task_id == "task-active").unwrap();
    assert_eq!(active_row.event_type, TaskLifecycleState::Running);

    // Terminal tasks must not appear.
    assert!(!task_ids.contains(&"task-done"));
    assert!(!task_ids.contains(&"task-failed"));
}

#[test]
fn subtask_events_for_parent_returns_latest_per_subtask() {
    let db = test_db();

    let parent_id = "parent-task-1";
    let other_parent = "parent-task-2";

    // subtask-a: transitions pending -> running
    let mut s1 = make_event("subtask-a", TaskLifecycleState::Pending);
    s1.id = "sa-1".to_string();
    s1.parent_task_id = Some(parent_id.to_string());
    s1.created_at = "2026-01-01 14:00:00".to_string();

    let mut s2 = make_event("subtask-a", TaskLifecycleState::Running);
    s2.id = "sa-2".to_string();
    s2.parent_task_id = Some(parent_id.to_string());
    s2.created_at = "2026-01-01 14:00:01".to_string();

    // subtask-b: single completed event
    let mut s3 = make_event("subtask-b", TaskLifecycleState::Completed);
    s3.id = "sb-1".to_string();
    s3.parent_task_id = Some(parent_id.to_string());
    s3.created_at = "2026-01-01 14:01:00".to_string();

    // subtask-c: belongs to a different parent — must NOT appear
    let mut s4 = make_event("subtask-c", TaskLifecycleState::Running);
    s4.id = "sc-1".to_string();
    s4.parent_task_id = Some(other_parent.to_string());
    s4.created_at = "2026-01-01 14:02:00".to_string();

    for e in [&s1, &s2, &s3, &s4] {
        insert_task_event(&db, e).unwrap();
    }

    let subtasks = subtask_events_for_parent(&db, parent_id).unwrap();

    // Only subtask-a and subtask-b belong to parent-task-1.
    assert_eq!(
        subtasks.len(),
        2,
        "expected 2 subtasks, got {}",
        subtasks.len()
    );

    let ids: Vec<&str> = subtasks.iter().map(|r| r.task_id.as_str()).collect();
    assert!(ids.contains(&"subtask-a"));
    assert!(ids.contains(&"subtask-b"));
    assert!(
        !ids.contains(&"subtask-c"),
        "subtask-c belongs to a different parent"
    );

    // Verify latest event for subtask-a is Running (not Pending).
    let a_row = subtasks.iter().find(|r| r.task_id == "subtask-a").unwrap();
    assert_eq!(a_row.event_type, TaskLifecycleState::Running);

    // Verify subtask-b shows Completed.
    let b_row = subtasks.iter().find(|r| r.task_id == "subtask-b").unwrap();
    assert_eq!(b_row.event_type, TaskLifecycleState::Completed);
}

// ---------------------------------------------------------------------------
// Reverse sync tests
// ---------------------------------------------------------------------------

/// Creates a test DB with both `task_events` and `tasks` tables so we can
/// verify the reverse sync from event insertion to task status updates.
fn test_db_with_tasks() -> Database {
    let db = test_db();
    db.conn()
        .execute_batch(
            "CREATE TABLE IF NOT EXISTS tasks ( \
                id TEXT PRIMARY KEY, \
                title TEXT NOT NULL, \
                description TEXT, \
                status TEXT NOT NULL DEFAULT 'pending', \
                priority INTEGER NOT NULL DEFAULT 0, \
                source TEXT, \
                created_at TEXT NOT NULL DEFAULT (datetime('now')), \
                updated_at TEXT NOT NULL DEFAULT (datetime('now')) \
            );",
        )
        .expect("tasks DDL");
    db
}

#[test]
fn insert_event_syncs_task_status_to_running() {
    let db = test_db_with_tasks();
    db.conn()
        .execute(
            "INSERT INTO tasks (id, title) VALUES ('task-1', 'Test task')",
            [],
        )
        .unwrap();

    // Verify initial status is pending
    let status: String = db
        .conn()
        .query_row("SELECT status FROM tasks WHERE id = 'task-1'", [], |r| {
            r.get(0)
        })
        .unwrap();
    assert_eq!(status, "pending");

    // Insert a Running event — should sync tasks.status to "in_progress"
    let event = make_event("task-1", TaskLifecycleState::Running);
    insert_task_event(&db, &event).unwrap();

    let status: String = db
        .conn()
        .query_row("SELECT status FROM tasks WHERE id = 'task-1'", [], |r| {
            r.get(0)
        })
        .unwrap();
    assert_eq!(status, "in_progress");
}

#[test]
fn insert_event_syncs_task_status_to_completed() {
    let db = test_db_with_tasks();
    db.conn()
        .execute(
            "INSERT INTO tasks (id, title) VALUES ('task-2', 'Another task')",
            [],
        )
        .unwrap();

    let event = make_event("task-2", TaskLifecycleState::Completed);
    insert_task_event(&db, &event).unwrap();

    let status: String = db
        .conn()
        .query_row("SELECT status FROM tasks WHERE id = 'task-2'", [], |r| {
            r.get(0)
        })
        .unwrap();
    assert_eq!(status, "completed");
}

#[test]
fn insert_progress_event_does_not_change_task_status() {
    let db = test_db_with_tasks();
    db.conn()
        .execute(
            "INSERT INTO tasks (id, title, status) VALUES ('task-3', 'Prog task', 'in_progress')",
            [],
        )
        .unwrap();

    let event = make_event("task-3", TaskLifecycleState::Progress);
    insert_task_event(&db, &event).unwrap();

    let status: String = db
        .conn()
        .query_row("SELECT status FROM tasks WHERE id = 'task-3'", [], |r| {
            r.get(0)
        })
        .unwrap();
    assert_eq!(
        status, "in_progress",
        "Progress events should not change task status"
    );
}

#[test]
fn insert_event_for_synthetic_task_id_does_not_error() {
    let db = test_db_with_tasks();
    // Synthetic task IDs from the pipeline (e.g. "{turn_id}-sub-0") don't
    // exist in the tasks table — the reverse sync should silently skip them.
    let event = make_event("turn-abc-sub-0", TaskLifecycleState::Completed);
    insert_task_event(&db, &event).unwrap(); // Should not panic or error
}
