use super::*;
use crate::gitlab::types::GitLabAuthor;
use rusqlite::Connection;

fn passes_cursor_filter(issue: &GitLabIssue, cursor: &SyncCursor) -> Result<bool> {
    let Some(cursor_ts) = cursor.updated_at_cursor else {
        return Ok(true);
    };

    let issue_ts = parse_timestamp(&issue.updated_at)?;

    if issue_ts < cursor_ts {
        return Ok(false);
    }

    if issue_ts == cursor_ts
        && cursor
            .tie_breaker_id
            .is_some_and(|cursor_id| issue.id <= cursor_id)
    {
        return Ok(false);
    }

    Ok(true)
}

fn make_test_issue(id: i64, updated_at: &str) -> GitLabIssue {
    GitLabIssue {
        id,
        iid: id,
        project_id: 100,
        title: format!("Issue {}", id),
        description: None,
        state: "opened".to_string(),
        created_at: "2024-01-01T00:00:00.000Z".to_string(),
        updated_at: updated_at.to_string(),
        closed_at: None,
        author: GitLabAuthor {
            id: 1,
            username: "test".to_string(),
            name: "Test".to_string(),
        },
        assignees: vec![],
        labels: vec![],
        milestone: None,
        due_date: None,
        web_url: "https://example.com".to_string(),
    }
}

fn setup_link_tables() -> Connection {
    let conn = Connection::open_in_memory().unwrap();
    conn.execute_batch(
        "
        CREATE TABLE issue_labels (
            issue_id INTEGER NOT NULL,
            label_id INTEGER NOT NULL,
            PRIMARY KEY(issue_id, label_id)
        );
        CREATE TABLE issue_assignees (
            issue_id INTEGER NOT NULL,
            username TEXT NOT NULL,
            PRIMARY KEY(issue_id, username)
        );
        ",
    )
    .unwrap();
    conn
}

#[test]
fn cursor_filter_allows_newer_issues() {
    let cursor = SyncCursor {
        updated_at_cursor: Some(1705312800000),
        tie_breaker_id: Some(100),
    };

    let issue = make_test_issue(101, "2024-01-16T10:00:00.000Z");
    assert!(passes_cursor_filter(&issue, &cursor).unwrap_or(false));
}

#[test]
fn cursor_filter_blocks_older_issues() {
    let cursor = SyncCursor {
        updated_at_cursor: Some(1705312800000),
        tie_breaker_id: Some(100),
    };

    let issue = make_test_issue(99, "2024-01-14T10:00:00.000Z");
    assert!(!passes_cursor_filter(&issue, &cursor).unwrap_or(true));
}

#[test]
fn cursor_filter_uses_tie_breaker_for_same_timestamp() {
    let cursor = SyncCursor {
        updated_at_cursor: Some(1705312800000),
        tie_breaker_id: Some(100),
    };

    let issue1 = make_test_issue(101, "2024-01-15T10:00:00.000Z");
    assert!(passes_cursor_filter(&issue1, &cursor).unwrap_or(false));

    let issue2 = make_test_issue(100, "2024-01-15T10:00:00.000Z");
    assert!(!passes_cursor_filter(&issue2, &cursor).unwrap_or(true));

    let issue3 = make_test_issue(99, "2024-01-15T10:00:00.000Z");
    assert!(!passes_cursor_filter(&issue3, &cursor).unwrap_or(true));
}

#[test]
fn cursor_filter_allows_all_when_no_cursor() {
    let cursor = SyncCursor::default();

    let issue = make_test_issue(1, "2020-01-01T00:00:00.000Z");
    assert!(passes_cursor_filter(&issue, &cursor).unwrap_or(false));
}

#[test]
fn batch_issue_label_insert_deduplicates_ids() {
    let conn = setup_link_tables();
    let tx = conn.unchecked_transaction().unwrap();

    link_issue_labels_batch_tx(&tx, 42, &[9, 3, 9, 1]).unwrap();
    tx.commit().unwrap();

    let ids: Vec<i64> = conn
        .prepare("SELECT label_id FROM issue_labels WHERE issue_id = 42 ORDER BY label_id")
        .unwrap()
        .query_map([], |row| row.get(0))
        .unwrap()
        .collect::<std::result::Result<Vec<_>, _>>()
        .unwrap();
    assert_eq!(ids, vec![1, 3, 9]);
}

#[test]
fn batch_issue_assignee_insert_deduplicates_usernames() {
    let conn = setup_link_tables();
    let tx = conn.unchecked_transaction().unwrap();

    let users = vec![
        "alice".to_string(),
        "bob".to_string(),
        "alice".to_string(),
        "carol".to_string(),
    ];
    insert_issue_assignees_batch_tx(&tx, 7, &users).unwrap();
    tx.commit().unwrap();

    let names: Vec<String> = conn
        .prepare("SELECT username FROM issue_assignees WHERE issue_id = 7 ORDER BY username")
        .unwrap()
        .query_map([], |row| row.get(0))
        .unwrap()
        .collect::<std::result::Result<Vec<_>, _>>()
        .unwrap();
    assert_eq!(names, vec!["alice", "bob", "carol"]);
}

#[test]
fn batch_issue_links_handle_multiple_chunks() {
    let conn = setup_link_tables();
    let tx = conn.unchecked_transaction().unwrap();

    let mut label_ids: Vec<i64> = (0..(BATCH_LINK_ROWS_MAX as i64 + 3))
        .map(|idx| idx + 10)
        .collect();
    label_ids.push(10);

    let mut users: Vec<String> = (0..(BATCH_LINK_ROWS_MAX + 5))
        .map(|idx| format!("user-{idx}"))
        .collect();
    users.push("user-0".to_string());

    link_issue_labels_batch_tx(&tx, 11, &label_ids).unwrap();
    insert_issue_assignees_batch_tx(&tx, 11, &users).unwrap();
    tx.commit().unwrap();

    let label_count: i64 = conn
        .query_row(
            "SELECT COUNT(*) FROM issue_labels WHERE issue_id = 11",
            [],
            |row| row.get(0),
        )
        .unwrap();
    let user_count: i64 = conn
        .query_row(
            "SELECT COUNT(*) FROM issue_assignees WHERE issue_id = 11",
            [],
            |row| row.get(0),
        )
        .unwrap();

    assert_eq!(label_count, BATCH_LINK_ROWS_MAX as i64 + 3);
    assert_eq!(user_count, (BATCH_LINK_ROWS_MAX + 5) as i64);
}
