use lore::gitlab::transformers::discussion::transform_notes_with_diff_position;
use lore::gitlab::types::{
    GitLabAuthor, GitLabDiscussion, GitLabLineRange, GitLabLineRangePoint, GitLabNote,
    GitLabNotePosition,
};

fn make_author() -> GitLabAuthor {
    GitLabAuthor {
        id: 1,
        username: "testuser".to_string(),
        name: "Test User".to_string(),
    }
}

fn make_basic_note(id: i64, created_at: &str) -> GitLabNote {
    GitLabNote {
        id,
        note_type: Some("DiscussionNote".to_string()),
        body: format!("Note {}", id),
        author: make_author(),
        created_at: created_at.to_string(),
        updated_at: created_at.to_string(),
        system: false,
        resolvable: false,
        resolved: false,
        resolved_by: None,
        resolved_at: None,
        position: None,
    }
}

fn make_diffnote_with_position(
    id: i64,
    created_at: &str,
    position: GitLabNotePosition,
) -> GitLabNote {
    GitLabNote {
        id,
        note_type: Some("DiffNote".to_string()),
        body: format!("DiffNote {}", id),
        author: make_author(),
        created_at: created_at.to_string(),
        updated_at: created_at.to_string(),
        system: false,
        resolvable: true,
        resolved: false,
        resolved_by: None,
        resolved_at: None,
        position: Some(position),
    }
}

fn make_discussion(notes: Vec<GitLabNote>) -> GitLabDiscussion {
    GitLabDiscussion {
        id: "abc123".to_string(),
        individual_note: false,
        notes,
    }
}

#[test]
fn extracts_position_paths_from_diffnote() {
    let position = GitLabNotePosition {
        old_path: Some("src/old.rs".to_string()),
        new_path: Some("src/new.rs".to_string()),
        old_line: Some(10),
        new_line: Some(15),
        position_type: Some("text".to_string()),
        line_range: None,
        base_sha: None,
        start_sha: None,
        head_sha: None,
    };
    let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
    let discussion = make_discussion(vec![note]);

    let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();

    assert_eq!(notes.len(), 1);
    assert_eq!(notes[0].position_old_path, Some("src/old.rs".to_string()));
    assert_eq!(notes[0].position_new_path, Some("src/new.rs".to_string()));
    assert_eq!(notes[0].position_old_line, Some(10));
    assert_eq!(notes[0].position_new_line, Some(15));
}

#[test]
fn extracts_position_type_from_diffnote() {
    let position = GitLabNotePosition {
        old_path: None,
        new_path: Some("image.png".to_string()),
        old_line: None,
        new_line: None,
        position_type: Some("image".to_string()),
        line_range: None,
        base_sha: None,
        start_sha: None,
        head_sha: None,
    };
    let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
    let discussion = make_discussion(vec![note]);

    let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();

    assert_eq!(notes[0].position_type, Some("image".to_string()));
}

#[test]
fn extracts_sha_triplet_from_diffnote() {
    let position = GitLabNotePosition {
        old_path: Some("file.rs".to_string()),
        new_path: Some("file.rs".to_string()),
        old_line: Some(5),
        new_line: Some(5),
        position_type: Some("text".to_string()),
        line_range: None,
        base_sha: Some("abc123base".to_string()),
        start_sha: Some("def456start".to_string()),
        head_sha: Some("ghi789head".to_string()),
    };
    let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
    let discussion = make_discussion(vec![note]);

    let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();

    assert_eq!(notes[0].position_base_sha, Some("abc123base".to_string()));
    assert_eq!(notes[0].position_start_sha, Some("def456start".to_string()));
    assert_eq!(notes[0].position_head_sha, Some("ghi789head".to_string()));
}

#[test]
fn extracts_line_range_from_multiline_diffnote() {
    let line_range = GitLabLineRange {
        start: GitLabLineRangePoint {
            line_code: Some("abc123_10_10".to_string()),
            line_type: Some("new".to_string()),
            old_line: None,
            new_line: Some(10),
        },
        end: GitLabLineRangePoint {
            line_code: Some("abc123_15_15".to_string()),
            line_type: Some("new".to_string()),
            old_line: None,
            new_line: Some(15),
        },
    };
    let position = GitLabNotePosition {
        old_path: None,
        new_path: Some("file.rs".to_string()),
        old_line: None,
        new_line: Some(10),
        position_type: Some("text".to_string()),
        line_range: Some(line_range),
        base_sha: None,
        start_sha: None,
        head_sha: None,
    };
    let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
    let discussion = make_discussion(vec![note]);

    let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();

    assert_eq!(notes[0].position_line_range_start, Some(10));
    assert_eq!(notes[0].position_line_range_end, Some(15));
}

#[test]
fn line_range_uses_old_line_fallback_when_new_line_missing() {
    let line_range = GitLabLineRange {
        start: GitLabLineRangePoint {
            line_code: None,
            line_type: Some("old".to_string()),
            old_line: Some(20),
            new_line: None,
        },
        end: GitLabLineRangePoint {
            line_code: None,
            line_type: Some("old".to_string()),
            old_line: Some(25),
            new_line: None,
        },
    };
    let position = GitLabNotePosition {
        old_path: Some("deleted.rs".to_string()),
        new_path: None,
        old_line: Some(20),
        new_line: None,
        position_type: Some("text".to_string()),
        line_range: Some(line_range),
        base_sha: None,
        start_sha: None,
        head_sha: None,
    };
    let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
    let discussion = make_discussion(vec![note]);

    let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();

    assert_eq!(notes[0].position_line_range_start, Some(20));
    assert_eq!(notes[0].position_line_range_end, Some(25));
}

#[test]
fn regular_note_has_none_for_all_position_fields() {
    let note = make_basic_note(1, "2024-01-16T09:00:00.000Z");
    let discussion = make_discussion(vec![note]);

    let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();

    assert_eq!(notes[0].position_old_path, None);
    assert_eq!(notes[0].position_new_path, None);
    assert_eq!(notes[0].position_old_line, None);
    assert_eq!(notes[0].position_new_line, None);
    assert_eq!(notes[0].position_type, None);
    assert_eq!(notes[0].position_line_range_start, None);
    assert_eq!(notes[0].position_line_range_end, None);
    assert_eq!(notes[0].position_base_sha, None);
    assert_eq!(notes[0].position_start_sha, None);
    assert_eq!(notes[0].position_head_sha, None);
}

#[test]
fn returns_error_for_invalid_created_at_timestamp() {
    let mut note = make_basic_note(1, "2024-01-16T09:00:00.000Z");
    note.created_at = "not-a-timestamp".to_string();
    let discussion = make_discussion(vec![note]);

    let result = transform_notes_with_diff_position(&discussion, 100);

    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(err.contains("not-a-timestamp"));
}

#[test]
fn returns_error_for_invalid_updated_at_timestamp() {
    let mut note = make_basic_note(1, "2024-01-16T09:00:00.000Z");
    note.updated_at = "garbage".to_string();
    let discussion = make_discussion(vec![note]);

    let result = transform_notes_with_diff_position(&discussion, 100);

    assert!(result.is_err());
}

#[test]
fn returns_error_for_invalid_resolved_at_timestamp() {
    let mut note = make_basic_note(1, "2024-01-16T09:00:00.000Z");
    note.resolvable = true;
    note.resolved = true;
    note.resolved_by = Some(make_author());
    note.resolved_at = Some("bad-timestamp".to_string());
    let discussion = make_discussion(vec![note]);

    let result = transform_notes_with_diff_position(&discussion, 100);

    assert!(result.is_err());
}

#[test]
fn handles_mixed_diffnote_and_regular_notes() {
    let position = GitLabNotePosition {
        old_path: None,
        new_path: Some("file.rs".to_string()),
        old_line: None,
        new_line: Some(42),
        position_type: Some("text".to_string()),
        line_range: None,
        base_sha: None,
        start_sha: None,
        head_sha: None,
    };
    let diffnote = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
    let regular_note = make_basic_note(2, "2024-01-16T10:00:00.000Z");
    let discussion = make_discussion(vec![diffnote, regular_note]);

    let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();

    assert_eq!(notes.len(), 2);
    assert_eq!(notes[0].position_new_path, Some("file.rs".to_string()));
    assert_eq!(notes[0].position_new_line, Some(42));
    assert_eq!(notes[1].position_new_path, None);
    assert_eq!(notes[1].position_new_line, None);
}

#[test]
fn preserves_note_position_index() {
    let pos1 = GitLabNotePosition {
        old_path: None,
        new_path: Some("file.rs".to_string()),
        old_line: None,
        new_line: Some(10),
        position_type: Some("text".to_string()),
        line_range: None,
        base_sha: None,
        start_sha: None,
        head_sha: None,
    };
    let pos2 = GitLabNotePosition {
        old_path: None,
        new_path: Some("file.rs".to_string()),
        old_line: None,
        new_line: Some(20),
        position_type: Some("text".to_string()),
        line_range: None,
        base_sha: None,
        start_sha: None,
        head_sha: None,
    };
    let note1 = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", pos1);
    let note2 = make_diffnote_with_position(2, "2024-01-16T10:00:00.000Z", pos2);
    let discussion = make_discussion(vec![note1, note2]);

    let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();

    assert_eq!(notes[0].position, 0);
    assert_eq!(notes[1].position, 1);
}

#[test]
fn handles_diffnote_with_empty_position_fields() {
    let position = GitLabNotePosition {
        old_path: None,
        new_path: None,
        old_line: None,
        new_line: None,
        position_type: None,
        line_range: None,
        base_sha: None,
        start_sha: None,
        head_sha: None,
    };
    let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
    let discussion = make_discussion(vec![note]);

    let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();

    assert_eq!(notes[0].position_old_path, None);
    assert_eq!(notes[0].position_new_path, None);
}

#[test]
fn handles_file_position_type() {
    let position = GitLabNotePosition {
        old_path: None,
        new_path: Some("binary.bin".to_string()),
        old_line: None,
        new_line: None,
        position_type: Some("file".to_string()),
        line_range: None,
        base_sha: None,
        start_sha: None,
        head_sha: None,
    };
    let note = make_diffnote_with_position(1, "2024-01-16T09:00:00.000Z", position);
    let discussion = make_discussion(vec![note]);

    let notes = transform_notes_with_diff_position(&discussion, 100).unwrap();

    assert_eq!(notes[0].position_type, Some("file".to_string()));
    assert_eq!(notes[0].position_new_path, Some("binary.bin".to_string()));
    assert_eq!(notes[0].position_new_line, None);
}
