use super::*;
use crate::core::db::{create_connection, run_migrations};
use std::path::Path;

fn setup_test_db() -> Connection {
    let conn = create_connection(Path::new(":memory:")).unwrap();
    run_migrations(&conn).unwrap();
    conn
}

#[test]
fn test_sync_run_recorder_start() {
    let conn = setup_test_db();
    let recorder = SyncRunRecorder::start(&conn, "sync", "abc12345").unwrap();
    assert!(recorder.row_id > 0);

    let (status, command, run_id): (String, String, String) = conn
        .query_row(
            "SELECT status, command, run_id FROM sync_runs WHERE id = ?1",
            [recorder.row_id],
            |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
        )
        .unwrap();

    assert_eq!(status, "running");
    assert_eq!(command, "sync");
    assert_eq!(run_id, "abc12345");
}

#[test]
fn test_sync_run_recorder_succeed() {
    let conn = setup_test_db();
    let recorder = SyncRunRecorder::start(&conn, "sync", "def67890").unwrap();
    let row_id = recorder.row_id;

    let metrics = vec![StageTiming {
        name: "ingest".to_string(),
        project: None,
        elapsed_ms: 1200,
        items_processed: 50,
        items_skipped: 0,
        errors: 2,
        rate_limit_hits: 0,
        retries: 0,
        sub_stages: vec![],
    }];

    recorder.succeed(&conn, &metrics, 50, 2).unwrap();

    let (status, finished_at, metrics_json, total_items, total_errors): (
        String,
        Option<i64>,
        Option<String>,
        i64,
        i64,
    ) = conn
        .query_row(
            "SELECT status, finished_at, metrics_json, total_items_processed, total_errors
             FROM sync_runs WHERE id = ?1",
            [row_id],
            |row| {
                Ok((
                    row.get(0)?,
                    row.get(1)?,
                    row.get(2)?,
                    row.get(3)?,
                    row.get(4)?,
                ))
            },
        )
        .unwrap();

    assert_eq!(status, "succeeded");
    assert!(finished_at.is_some());
    assert!(metrics_json.is_some());
    assert_eq!(total_items, 50);
    assert_eq!(total_errors, 2);

    let parsed: Vec<StageTiming> = serde_json::from_str(&metrics_json.unwrap()).unwrap();
    assert_eq!(parsed.len(), 1);
    assert_eq!(parsed[0].name, "ingest");
}

#[test]
fn test_sync_run_recorder_fail() {
    let conn = setup_test_db();
    let recorder = SyncRunRecorder::start(&conn, "ingest issues", "fail0001").unwrap();
    let row_id = recorder.row_id;

    recorder.fail(&conn, "GitLab auth failed", None).unwrap();

    let (status, finished_at, error, metrics_json): (
        String,
        Option<i64>,
        Option<String>,
        Option<String>,
    ) = conn
        .query_row(
            "SELECT status, finished_at, error, metrics_json
             FROM sync_runs WHERE id = ?1",
            [row_id],
            |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
        )
        .unwrap();

    assert_eq!(status, "failed");
    assert!(finished_at.is_some());
    assert_eq!(error.as_deref(), Some("GitLab auth failed"));
    assert!(metrics_json.is_none());
}

#[test]
fn test_sync_run_recorder_fail_with_partial_metrics() {
    let conn = setup_test_db();
    let recorder = SyncRunRecorder::start(&conn, "sync", "part0001").unwrap();
    let row_id = recorder.row_id;

    let partial_metrics = vec![StageTiming {
        name: "ingest_issues".to_string(),
        project: Some("group/repo".to_string()),
        elapsed_ms: 800,
        items_processed: 30,
        items_skipped: 0,
        errors: 0,
        rate_limit_hits: 1,
        retries: 0,
        sub_stages: vec![],
    }];

    recorder
        .fail(&conn, "Embedding failed", Some(&partial_metrics))
        .unwrap();

    let (status, metrics_json): (String, Option<String>) = conn
        .query_row(
            "SELECT status, metrics_json FROM sync_runs WHERE id = ?1",
            [row_id],
            |row| Ok((row.get(0)?, row.get(1)?)),
        )
        .unwrap();

    assert_eq!(status, "failed");
    assert!(metrics_json.is_some());

    let parsed: Vec<StageTiming> = serde_json::from_str(&metrics_json.unwrap()).unwrap();
    assert_eq!(parsed.len(), 1);
    assert_eq!(parsed[0].name, "ingest_issues");
}

#[test]
fn sync_run_surgical_columns_exist() {
    let conn = setup_test_db();
    conn.execute(
        "INSERT INTO sync_runs (started_at, heartbeat_at, status, command, mode, phase, surgical_iids_json)
         VALUES (1000, 1000, 'running', 'sync', 'surgical', 'preflight', '{\"issues\":[7],\"mrs\":[]}')",
        [],
    )
    .unwrap();
    let (mode, phase, iids_json): (String, String, String) = conn
        .query_row(
            "SELECT mode, phase, surgical_iids_json FROM sync_runs WHERE mode = 'surgical'",
            [],
            |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)),
        )
        .unwrap();
    assert_eq!(mode, "surgical");
    assert_eq!(phase, "preflight");
    assert!(iids_json.contains("7"));
}

#[test]
fn sync_run_counter_defaults_are_zero() {
    let conn = setup_test_db();
    conn.execute(
        "INSERT INTO sync_runs (started_at, heartbeat_at, status, command)
         VALUES (2000, 2000, 'running', 'sync')",
        [],
    )
    .unwrap();
    let row_id = conn.last_insert_rowid();
    let (issues_fetched, mrs_fetched, docs_regenerated, warnings_count): (i64, i64, i64, i64) =
        conn.query_row(
            "SELECT issues_fetched, mrs_fetched, docs_regenerated, warnings_count FROM sync_runs WHERE id = ?1",
            [row_id],
            |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
        )
        .unwrap();
    assert_eq!(issues_fetched, 0);
    assert_eq!(mrs_fetched, 0);
    assert_eq!(docs_regenerated, 0);
    assert_eq!(warnings_count, 0);
}

#[test]
fn sync_run_nullable_columns_default_to_null() {
    let conn = setup_test_db();
    conn.execute(
        "INSERT INTO sync_runs (started_at, heartbeat_at, status, command)
         VALUES (3000, 3000, 'running', 'sync')",
        [],
    )
    .unwrap();
    let row_id = conn.last_insert_rowid();
    let (mode, phase, cancelled_at): (Option<String>, Option<String>, Option<i64>) = conn
        .query_row(
            "SELECT mode, phase, cancelled_at FROM sync_runs WHERE id = ?1",
            [row_id],
            |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)),
        )
        .unwrap();
    assert!(mode.is_none());
    assert!(phase.is_none());
    assert!(cancelled_at.is_none());
}

#[test]
fn sync_run_counter_round_trip() {
    let conn = setup_test_db();
    conn.execute(
        "INSERT INTO sync_runs (started_at, heartbeat_at, status, command, mode, issues_fetched, mrs_ingested, docs_embedded)
         VALUES (4000, 4000, 'succeeded', 'sync', 'surgical', 3, 2, 5)",
        [],
    )
    .unwrap();
    let row_id = conn.last_insert_rowid();
    let (issues_fetched, mrs_ingested, docs_embedded): (i64, i64, i64) = conn
        .query_row(
            "SELECT issues_fetched, mrs_ingested, docs_embedded FROM sync_runs WHERE id = ?1",
            [row_id],
            |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)),
        )
        .unwrap();
    assert_eq!(issues_fetched, 3);
    assert_eq!(mrs_ingested, 2);
    assert_eq!(docs_embedded, 5);
}

#[test]
fn surgical_lifecycle_start_metadata_succeed() {
    let conn = setup_test_db();
    let recorder = SyncRunRecorder::start(&conn, "sync", "surg001").unwrap();
    let row_id = recorder.row_id();

    recorder
        .set_surgical_metadata(
            &conn,
            "surgical",
            "preflight",
            r#"{"issues":[7,8],"mrs":[101]}"#,
        )
        .unwrap();

    recorder.update_phase(&conn, "ingest").unwrap();
    recorder
        .record_entity_result(&conn, "issue", "fetched")
        .unwrap();
    recorder
        .record_entity_result(&conn, "issue", "fetched")
        .unwrap();
    recorder
        .record_entity_result(&conn, "issue", "ingested")
        .unwrap();
    recorder
        .record_entity_result(&conn, "mr", "fetched")
        .unwrap();
    recorder
        .record_entity_result(&conn, "mr", "ingested")
        .unwrap();

    recorder.succeed(&conn, &[], 3, 0).unwrap();

    #[allow(clippy::type_complexity)]
    let (mode, phase, iids, issues_fetched, mrs_fetched, issues_ingested, mrs_ingested, status): (
        String,
        String,
        String,
        i64,
        i64,
        i64,
        i64,
        String,
    ) = conn
        .query_row(
            "SELECT mode, phase, surgical_iids_json, issues_fetched, mrs_fetched, \
             issues_ingested, mrs_ingested, status \
             FROM sync_runs WHERE id = ?1",
            [row_id],
            |r| {
                Ok((
                    r.get(0)?,
                    r.get(1)?,
                    r.get(2)?,
                    r.get(3)?,
                    r.get(4)?,
                    r.get(5)?,
                    r.get(6)?,
                    r.get(7)?,
                ))
            },
        )
        .unwrap();

    assert_eq!(mode, "surgical");
    assert_eq!(phase, "ingest");
    assert!(iids.contains("101"));
    assert_eq!(issues_fetched, 2);
    assert_eq!(mrs_fetched, 1);
    assert_eq!(issues_ingested, 1);
    assert_eq!(mrs_ingested, 1);
    assert_eq!(status, "succeeded");
}

#[test]
fn surgical_lifecycle_cancel() {
    let conn = setup_test_db();
    let recorder = SyncRunRecorder::start(&conn, "sync", "cancel01").unwrap();
    let row_id = recorder.row_id();

    recorder
        .set_surgical_metadata(&conn, "surgical", "preflight", "{}")
        .unwrap();
    recorder
        .cancel(&conn, "User requested cancellation")
        .unwrap();

    let (status, error, cancelled_at, finished_at): (
        String,
        Option<String>,
        Option<i64>,
        Option<i64>,
    ) = conn
        .query_row(
            "SELECT status, error, cancelled_at, finished_at FROM sync_runs WHERE id = ?1",
            [row_id],
            |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
        )
        .unwrap();

    assert_eq!(status, "cancelled");
    assert_eq!(error.as_deref(), Some("User requested cancellation"));
    assert!(cancelled_at.is_some());
    assert!(finished_at.is_some());
}

#[test]
fn record_entity_result_ignores_unknown() {
    let conn = setup_test_db();
    let recorder = SyncRunRecorder::start(&conn, "sync", "unk001").unwrap();
    recorder
        .record_entity_result(&conn, "widget", "exploded")
        .unwrap();
}

#[test]
fn record_entity_result_doc_counters() {
    let conn = setup_test_db();
    let recorder = SyncRunRecorder::start(&conn, "sync", "cnt001").unwrap();
    let row_id = recorder.row_id();

    recorder
        .record_entity_result(&conn, "doc", "regenerated")
        .unwrap();
    recorder
        .record_entity_result(&conn, "doc", "regenerated")
        .unwrap();
    recorder
        .record_entity_result(&conn, "doc", "embedded")
        .unwrap();
    recorder
        .record_entity_result(&conn, "issue", "skipped_stale")
        .unwrap();

    let (docs_regen, docs_embed, skipped): (i64, i64, i64) = conn
        .query_row(
            "SELECT docs_regenerated, docs_embedded, skipped_stale FROM sync_runs WHERE id = ?1",
            [row_id],
            |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)),
        )
        .unwrap();

    assert_eq!(docs_regen, 2);
    assert_eq!(docs_embed, 1);
    assert_eq!(skipped, 1);
}
