use super::*;
use crate::core::db::run_migrations;
use std::path::Path;

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

fn seed_project(conn: &Connection) {
    conn.execute(
            "INSERT INTO projects (id, gitlab_project_id, path_with_namespace, web_url, created_at, updated_at)
             VALUES (1, 100, 'group/repo', 'https://gitlab.example.com', 1000, 2000)",
            [],
        )
        .unwrap();
}

fn seed_issue(conn: &Connection) {
    seed_project(conn);
    conn.execute(
        "INSERT INTO issues (id, gitlab_id, iid, project_id, title, state, author_username,
             created_at, updated_at, last_seen_at)
             VALUES (1, 200, 10, 1, 'Test issue', 'opened', 'author', 1000, 2000, 2000)",
        [],
    )
    .unwrap();
}

fn seed_second_project(conn: &Connection) {
    conn.execute(
            "INSERT INTO projects (id, gitlab_project_id, path_with_namespace, web_url, created_at, updated_at)
             VALUES (2, 101, 'other/repo', 'https://gitlab.example.com/other', 1000, 2000)",
            [],
        )
        .unwrap();
}

fn seed_discussion_with_notes(
    conn: &Connection,
    issue_id: i64,
    project_id: i64,
    user_notes: usize,
    system_notes: usize,
) {
    let disc_id: i64 = conn
        .query_row(
            "SELECT COALESCE(MAX(id), 0) + 1 FROM discussions",
            [],
            |r| r.get(0),
        )
        .unwrap();
    conn.execute(
            "INSERT INTO discussions (id, gitlab_discussion_id, project_id, issue_id, noteable_type, first_note_at, last_note_at, last_seen_at)
             VALUES (?1, ?2, ?3, ?4, 'Issue', 1000, 2000, 2000)",
            rusqlite::params![disc_id, format!("disc-{}", disc_id), project_id, issue_id],
        )
        .unwrap();
    for i in 0..user_notes {
        conn.execute(
                "INSERT INTO notes (gitlab_id, discussion_id, project_id, author_username, body, created_at, updated_at, last_seen_at, is_system, position)
                 VALUES (?1, ?2, ?3, 'user1', 'comment', 1000, 2000, 2000, 0, ?4)",
                rusqlite::params![1000 + disc_id * 100 + i as i64, disc_id, project_id, i as i64],
            )
            .unwrap();
    }
    for i in 0..system_notes {
        conn.execute(
                "INSERT INTO notes (gitlab_id, discussion_id, project_id, author_username, body, created_at, updated_at, last_seen_at, is_system, position)
                 VALUES (?1, ?2, ?3, 'system', 'status changed', 1000, 2000, 2000, 1, ?4)",
                rusqlite::params![2000 + disc_id * 100 + i as i64, disc_id, project_id, (user_notes + i) as i64],
            )
            .unwrap();
    }
}

// --- find_issue tests ---

#[test]
fn test_find_issue_basic() {
    let conn = setup_test_db();
    seed_issue(&conn);
    let row = find_issue(&conn, 10, None).unwrap();
    assert_eq!(row.iid, 10);
    assert_eq!(row.title, "Test issue");
    assert_eq!(row.state, "opened");
    assert_eq!(row.author_username, "author");
    assert_eq!(row.project_path, "group/repo");
}

#[test]
fn test_find_issue_with_project_filter() {
    let conn = setup_test_db();
    seed_issue(&conn);
    let row = find_issue(&conn, 10, Some("group/repo")).unwrap();
    assert_eq!(row.iid, 10);
    assert_eq!(row.project_path, "group/repo");
}

#[test]
fn test_find_issue_not_found() {
    let conn = setup_test_db();
    seed_issue(&conn);
    let err = find_issue(&conn, 999, None).unwrap_err();
    assert!(matches!(err, LoreError::NotFound(_)));
}

#[test]
fn test_find_issue_wrong_project_filter() {
    let conn = setup_test_db();
    seed_issue(&conn);
    seed_second_project(&conn);
    // Issue 10 only exists in project 1, not project 2
    let err = find_issue(&conn, 10, Some("other/repo")).unwrap_err();
    assert!(matches!(err, LoreError::NotFound(_)));
}

#[test]
fn test_find_issue_ambiguous_without_project() {
    let conn = setup_test_db();
    seed_issue(&conn); // issue iid=10 in project 1
    seed_second_project(&conn);
    conn.execute(
            "INSERT INTO issues (id, gitlab_id, iid, project_id, title, state, author_username,
             created_at, updated_at, last_seen_at)
             VALUES (2, 201, 10, 2, 'Same iid different project', 'opened', 'author', 1000, 2000, 2000)",
            [],
        )
        .unwrap();
    let err = find_issue(&conn, 10, None).unwrap_err();
    assert!(matches!(err, LoreError::Ambiguous(_)));
}

#[test]
fn test_find_issue_ambiguous_resolved_with_project() {
    let conn = setup_test_db();
    seed_issue(&conn);
    seed_second_project(&conn);
    conn.execute(
            "INSERT INTO issues (id, gitlab_id, iid, project_id, title, state, author_username,
             created_at, updated_at, last_seen_at)
             VALUES (2, 201, 10, 2, 'Same iid different project', 'opened', 'author', 1000, 2000, 2000)",
            [],
        )
        .unwrap();
    let row = find_issue(&conn, 10, Some("other/repo")).unwrap();
    assert_eq!(row.title, "Same iid different project");
}

#[test]
fn test_find_issue_user_notes_count_zero() {
    let conn = setup_test_db();
    seed_issue(&conn);
    let row = find_issue(&conn, 10, None).unwrap();
    assert_eq!(row.user_notes_count, 0);
}

#[test]
fn test_find_issue_user_notes_count_excludes_system() {
    let conn = setup_test_db();
    seed_issue(&conn);
    // 2 user notes + 3 system notes = should count only 2
    seed_discussion_with_notes(&conn, 1, 1, 2, 3);
    let row = find_issue(&conn, 10, None).unwrap();
    assert_eq!(row.user_notes_count, 2);
}

#[test]
fn test_find_issue_user_notes_count_across_discussions() {
    let conn = setup_test_db();
    seed_issue(&conn);
    seed_discussion_with_notes(&conn, 1, 1, 3, 0); // 3 user notes
    seed_discussion_with_notes(&conn, 1, 1, 1, 2); // 1 user note + 2 system
    let row = find_issue(&conn, 10, None).unwrap();
    assert_eq!(row.user_notes_count, 4);
}

#[test]
fn test_find_issue_notes_count_ignores_other_issues() {
    let conn = setup_test_db();
    seed_issue(&conn);
    // Add a second issue
    conn.execute(
        "INSERT INTO issues (id, gitlab_id, iid, project_id, title, state, author_username,
             created_at, updated_at, last_seen_at)
             VALUES (2, 201, 20, 1, 'Other issue', 'opened', 'author', 1000, 2000, 2000)",
        [],
    )
    .unwrap();
    // Notes on issue 2, not issue 1
    seed_discussion_with_notes(&conn, 2, 1, 5, 0);
    let row = find_issue(&conn, 10, None).unwrap();
    assert_eq!(row.user_notes_count, 0); // Issue 10 has no notes
}

#[test]
fn test_ansi256_from_rgb() {
    // Moved to render.rs — keeping basic hex sanity check
    let result = render::style_with_hex("test", Some("#ff0000"));
    assert!(!result.is_empty());
}

#[test]
fn test_get_issue_assignees_empty() {
    let conn = setup_test_db();
    seed_issue(&conn);
    let result = get_issue_assignees(&conn, 1).unwrap();
    assert!(result.is_empty());
}

#[test]
fn test_get_issue_assignees_single() {
    let conn = setup_test_db();
    seed_issue(&conn);
    conn.execute(
        "INSERT INTO issue_assignees (issue_id, username) VALUES (1, 'charlie')",
        [],
    )
    .unwrap();
    let result = get_issue_assignees(&conn, 1).unwrap();
    assert_eq!(result, vec!["charlie"]);
}

#[test]
fn test_get_issue_assignees_multiple_sorted() {
    let conn = setup_test_db();
    seed_issue(&conn);
    conn.execute(
        "INSERT INTO issue_assignees (issue_id, username) VALUES (1, 'bob')",
        [],
    )
    .unwrap();
    conn.execute(
        "INSERT INTO issue_assignees (issue_id, username) VALUES (1, 'alice')",
        [],
    )
    .unwrap();
    let result = get_issue_assignees(&conn, 1).unwrap();
    assert_eq!(result, vec!["alice", "bob"]); // alphabetical
}

#[test]
fn test_get_closing_mrs_empty() {
    let conn = setup_test_db();
    seed_issue(&conn);
    let result = get_closing_mrs(&conn, 1).unwrap();
    assert!(result.is_empty());
}

#[test]
fn test_get_closing_mrs_single() {
    let conn = setup_test_db();
    seed_issue(&conn);
    conn.execute(
            "INSERT INTO merge_requests (id, gitlab_id, iid, project_id, title, state, author_username,
             source_branch, target_branch, created_at, updated_at, last_seen_at)
             VALUES (1, 300, 5, 1, 'Fix the bug', 'merged', 'dev', 'fix', 'main', 1000, 2000, 2000)",
            [],
        )
        .unwrap();
    conn.execute(
        "INSERT INTO entity_references (project_id, source_entity_type, source_entity_id,
             target_entity_type, target_entity_id, reference_type, source_method, created_at)
             VALUES (1, 'merge_request', 1, 'issue', 1, 'closes', 'api', 3000)",
        [],
    )
    .unwrap();
    let result = get_closing_mrs(&conn, 1).unwrap();
    assert_eq!(result.len(), 1);
    assert_eq!(result[0].iid, 5);
    assert_eq!(result[0].title, "Fix the bug");
    assert_eq!(result[0].state, "merged");
}

#[test]
fn test_get_closing_mrs_ignores_mentioned() {
    let conn = setup_test_db();
    seed_issue(&conn);
    // Add a 'mentioned' reference that should be ignored
    conn.execute(
        "INSERT INTO merge_requests (id, gitlab_id, iid, project_id, title, state, author_username,
             source_branch, target_branch, created_at, updated_at, last_seen_at)
             VALUES (1, 300, 5, 1, 'Some MR', 'opened', 'dev', 'feat', 'main', 1000, 2000, 2000)",
        [],
    )
    .unwrap();
    conn.execute(
        "INSERT INTO entity_references (project_id, source_entity_type, source_entity_id,
             target_entity_type, target_entity_id, reference_type, source_method, created_at)
             VALUES (1, 'merge_request', 1, 'issue', 1, 'mentioned', 'note_parse', 3000)",
        [],
    )
    .unwrap();
    let result = get_closing_mrs(&conn, 1).unwrap();
    assert!(result.is_empty()); // 'mentioned' refs not included
}

#[test]
fn test_get_closing_mrs_multiple_sorted() {
    let conn = setup_test_db();
    seed_issue(&conn);
    conn.execute(
            "INSERT INTO merge_requests (id, gitlab_id, iid, project_id, title, state, author_username,
             source_branch, target_branch, created_at, updated_at, last_seen_at)
             VALUES (1, 300, 8, 1, 'Second fix', 'opened', 'dev', 'fix2', 'main', 1000, 2000, 2000)",
            [],
        )
        .unwrap();
    conn.execute(
        "INSERT INTO merge_requests (id, gitlab_id, iid, project_id, title, state, author_username,
             source_branch, target_branch, created_at, updated_at, last_seen_at)
             VALUES (2, 301, 5, 1, 'First fix', 'merged', 'dev', 'fix1', 'main', 1000, 2000, 2000)",
        [],
    )
    .unwrap();
    conn.execute(
        "INSERT INTO entity_references (project_id, source_entity_type, source_entity_id,
             target_entity_type, target_entity_id, reference_type, source_method, created_at)
             VALUES (1, 'merge_request', 1, 'issue', 1, 'closes', 'api', 3000)",
        [],
    )
    .unwrap();
    conn.execute(
        "INSERT INTO entity_references (project_id, source_entity_type, source_entity_id,
             target_entity_type, target_entity_id, reference_type, source_method, created_at)
             VALUES (1, 'merge_request', 2, 'issue', 1, 'closes', 'api', 3000)",
        [],
    )
    .unwrap();
    let result = get_closing_mrs(&conn, 1).unwrap();
    assert_eq!(result.len(), 2);
    assert_eq!(result[0].iid, 5); // Lower iid first
    assert_eq!(result[1].iid, 8);
}

#[test]
fn wrap_text_single_line() {
    assert_eq!(wrap_text("hello world", 80, "  "), "hello world");
}

#[test]
fn wrap_text_multiple_lines() {
    let result = wrap_text("one two three four five", 10, "  ");
    assert!(result.contains('\n'));
}

#[test]
fn format_date_extracts_date_part() {
    let ms = 1705276800000;
    let date = format_date(ms);
    assert!(date.starts_with("2024-01-15"));
}
