use super::*;

fn setup_db() -> Connection {
    let conn = Connection::open_in_memory().unwrap();
    conn.execute_batch("
        CREATE TABLE dirty_sources (
            source_type TEXT NOT NULL CHECK (source_type IN ('issue','merge_request','discussion','note')),
            source_id INTEGER NOT NULL,
            queued_at INTEGER NOT NULL,
            attempt_count INTEGER NOT NULL DEFAULT 0,
            last_attempt_at INTEGER,
            last_error TEXT,
            next_attempt_at INTEGER,
            PRIMARY KEY(source_type, source_id)
        );
        CREATE INDEX idx_dirty_sources_next_attempt ON dirty_sources(next_attempt_at);
    ").unwrap();
    conn
}

#[test]
fn test_mark_dirty_inserts() {
    let conn = setup_db();
    mark_dirty(&conn, SourceType::Issue, 1).unwrap();

    let count: i64 = conn
        .query_row("SELECT COUNT(*) FROM dirty_sources", [], |r| r.get(0))
        .unwrap();
    assert_eq!(count, 1);
}

#[test]
fn test_mark_dirty_tx_inserts() {
    let mut conn = setup_db();
    {
        let tx = conn.transaction().unwrap();
        mark_dirty_tx(&tx, SourceType::Issue, 1).unwrap();
        tx.commit().unwrap();
    }
    let count: i64 = conn
        .query_row("SELECT COUNT(*) FROM dirty_sources", [], |r| r.get(0))
        .unwrap();
    assert_eq!(count, 1);
}

#[test]
fn test_requeue_resets_backoff() {
    let conn = setup_db();
    mark_dirty(&conn, SourceType::Issue, 1).unwrap();
    record_dirty_error(&conn, SourceType::Issue, 1, "test error").unwrap();

    let attempt: i64 = conn
        .query_row(
            "SELECT attempt_count FROM dirty_sources WHERE source_id = 1",
            [],
            |r| r.get(0),
        )
        .unwrap();
    assert_eq!(attempt, 1);

    mark_dirty(&conn, SourceType::Issue, 1).unwrap();
    let attempt: i64 = conn
        .query_row(
            "SELECT attempt_count FROM dirty_sources WHERE source_id = 1",
            [],
            |r| r.get(0),
        )
        .unwrap();
    assert_eq!(attempt, 0);

    let next_at: Option<i64> = conn
        .query_row(
            "SELECT next_attempt_at FROM dirty_sources WHERE source_id = 1",
            [],
            |r| r.get(0),
        )
        .unwrap();
    assert!(next_at.is_none());
}

#[test]
fn test_get_respects_backoff() {
    let conn = setup_db();
    mark_dirty(&conn, SourceType::Issue, 1).unwrap();
    conn.execute(
        "UPDATE dirty_sources SET next_attempt_at = 9999999999999 WHERE source_id = 1",
        [],
    )
    .unwrap();

    let results = get_dirty_sources(&conn).unwrap();
    assert!(results.is_empty());
}

#[test]
fn test_get_orders_by_attempt_count() {
    let conn = setup_db();
    mark_dirty(&conn, SourceType::Issue, 1).unwrap();
    conn.execute(
        "UPDATE dirty_sources SET attempt_count = 2 WHERE source_id = 1",
        [],
    )
    .unwrap();
    mark_dirty(&conn, SourceType::Issue, 2).unwrap();

    let results = get_dirty_sources(&conn).unwrap();
    assert_eq!(results.len(), 2);
    assert_eq!(results[0].1, 2);
    assert_eq!(results[1].1, 1);
}

#[test]
fn test_batch_size_500() {
    let conn = setup_db();
    for i in 0..600 {
        mark_dirty(&conn, SourceType::Issue, i).unwrap();
    }
    let results = get_dirty_sources(&conn).unwrap();
    assert_eq!(results.len(), 500);
}

#[test]
fn test_clear_removes() {
    let conn = setup_db();
    mark_dirty(&conn, SourceType::Issue, 1).unwrap();
    clear_dirty(&conn, SourceType::Issue, 1).unwrap();

    let count: i64 = conn
        .query_row("SELECT COUNT(*) FROM dirty_sources", [], |r| r.get(0))
        .unwrap();
    assert_eq!(count, 0);
}

#[test]
fn test_mark_dirty_note_type() {
    let conn = setup_db();
    mark_dirty(&conn, SourceType::Note, 42).unwrap();

    let results = get_dirty_sources(&conn).unwrap();
    assert_eq!(results.len(), 1);
    assert_eq!(results[0].0, SourceType::Note);
    assert_eq!(results[0].1, 42);

    clear_dirty(&conn, SourceType::Note, 42).unwrap();
    let results = get_dirty_sources(&conn).unwrap();
    assert!(results.is_empty());
}

#[test]
fn test_drain_loop() {
    let conn = setup_db();
    for i in 0..1200 {
        mark_dirty(&conn, SourceType::Issue, i).unwrap();
    }

    let mut total = 0;
    loop {
        let batch = get_dirty_sources(&conn).unwrap();
        if batch.is_empty() {
            break;
        }
        for (st, id) in &batch {
            clear_dirty(&conn, *st, *id).unwrap();
        }
        total += batch.len();
    }
    assert_eq!(total, 1200);
}
