use super::{helpers::*, home::heat_cells, *};

#[test]
fn repository_directories_sort_before_files() {
    let contents = [
        ("z.swift", "file"),
        ("Sources", "dir"),
        ("README.md", "file"),
    ]
    .into_iter()
    .map(|(name, kind)| models::ContentsResponse {
        name: Some(name.into()),
        path: Some(name.into()),
        r#type: Some(kind.into()),
        ..Default::default()
    })
    .collect();

    assert_eq!(
        repository_content_rows(contents)
            .into_iter()
            .map(|row| row.name)
            .collect::<Vec<_>>(),
        ["Sources", "README.md", "z.swift"]
    );
}

#[test]
fn presents_complete_commit_details() {
    let page = commit_details_page(
        models::Commit {
            sha: Some("0123456789abcdef".into()),
            commit: Some(Box::new(models::RepoCommit {
                message: Some("Add details\n\nExplain the change.".into()),
                author: Some(Box::new(models::CommitUser {
                    name: Some("Ada".into()),
                    email: Some("ada@example.com".into()),
                    date: Some("2026-08-01T10:00:00Z".into()),
                })),
                committer: Some(Box::new(models::CommitUser {
                    name: Some("Grace".into()),
                    date: Some("2026-08-02T11:00:00Z".into()),
                    ..Default::default()
                })),
                verification: Some(Box::new(models::PayloadCommitVerification {
                    verified: Some(true),
                    signature: Some("signed".into()),
                    ..Default::default()
                })),
                ..Default::default()
            })),
            files: Some(vec![models::CommitAffectedFiles {
                filename: Some("README.md".into()),
                status: Some("modified".into()),
            }]),
            ..Default::default()
        },
        Some("main".into()),
    );

    assert_eq!(page.title, "Add details");
    assert_eq!(page.description, "Explain the change.");
    assert_eq!(page.files[0].path, "README.md");
    assert_eq!(
        page.metadata
            .iter()
            .map(|row| (row.label.as_str(), row.value.as_str()))
            .collect::<Vec<_>>(),
        [
            ("Author", "Ada <ada@example.com>"),
            ("Committer", "Grace"),
            ("Committed", "2026-08-02"),
            ("Branch", "main"),
            ("Commit", "0123456789abcdef"),
            ("Signature", "Verified"),
        ]
    );
    assert_eq!(
        commit_details_page(models::Commit::default(), None)
            .metadata
            .last()
            .unwrap()
            .value,
        "Unsigned"
    );
}

#[test]
fn maps_heat_levels_and_labels() {
    let heatmap = vec![
        models::UserHeatmapData {
            timestamp: Some(20_393 * 86_400),
            contributions: Some(1),
        },
        models::UserHeatmapData {
            timestamp: Some(20_665 * 86_400),
            contributions: Some(4),
        },
    ];
    let (cells, total) = heat_cells(&heatmap);
    assert_eq!((cells.len(), total), (273, 5));
    assert_eq!(
        (cells[0].level, cells[0].timestamp, cells[272].level,),
        (1, 20_393 * 86_400, 4)
    );

    let label = models::Label {
        name: Some("bug".into()),
        color: Some("d73a4a".into()),
        ..Default::default()
    };
    assert!(label_row(&label).is_some());
    assert!(label_row(&models::Label::default()).is_none());

    let milestone = models::Milestone {
        id: Some(1),
        title: Some("Version 1".into()),
        open_issues: Some(3),
        closed_issues: Some(2),
        state: Some("open".into()),
        ..Default::default()
    };
    let milestone_row = milestone_rows(std::slice::from_ref(&milestone)).remove(0);
    assert_eq!(milestone_row.progress, 0.4);
    assert_eq!(milestone_row.state, WorkItemState::Open);
    assert_eq!(milestone_row.progress_accessibility, "2 closed, 3 open");
    let issue = models::Issue {
        state: Some("closed".into()),
        milestone: Some(Box::new(milestone)),
        ..Default::default()
    };
    let row = &issue_rows(&[issue])[0];
    assert_eq!(row.state, WorkItemState::Closed);
    assert_eq!(row.milestone, "Version 1");
}

#[test]
fn presents_filtered_home_activity_and_continuation() {
    use models::activity::OpType;

    let page = home_page(
        "Gitea".into(),
        HomeData {
            activities: [OpType::CreatePullRequest, OpType::ClosePullRequest]
                .into_iter()
                .map(|op_type| models::Activity {
                    op_type: Some(op_type),
                    ..Default::default()
                })
                .collect(),
            heatmap: Vec::new(),
            next_page: Some(4),
        },
    );

    assert_eq!(page.activities.len(), 2);
    assert_eq!(page.next_page, Some(4));
}

#[test]
fn presents_server_activity_with_actor_and_navigation_target() {
    use models::activity::OpType;

    let page = server_activity_page(gotcha_gitea::Page {
        items: vec![models::Activity {
            act_user: Some(Box::new(models::User {
                login: Some("apple".into()),
                ..Default::default()
            })),
            op_type: Some(OpType::CreateIssue),
            content: Some("1|Test issue".into()),
            repo: Some(Box::new(models::Repository {
                full_name: Some("apple/SimpleDemoRepo".into()),
                ..Default::default()
            })),
            ..Default::default()
        }],
        has_more: true,
    });

    assert_eq!(
        page.rows[0].title,
        "@apple · Opened an issue in apple/SimpleDemoRepo"
    );
    assert_eq!(page.rows[0].target, ActivityTargetKind::Issue);
    assert_eq!(page.rows[0].owner, "apple");
    assert_eq!(page.rows[0].repository, "SimpleDemoRepo");
    assert_eq!(page.rows[0].number, 1);
    assert!(page.has_more);
}

#[test]
fn issue_page_exposes_state_and_owned_comment_editing() {
    let page = issue_page(IssueDetails {
        issue: models::Issue {
            state: Some("closed".into()),
            labels: Some(vec![models::Label {
                name: Some("bug".into()),
                color: Some("ff0000".into()),
                ..Default::default()
            }]),
            ..Default::default()
        },
        comments: vec![
            models::Comment {
                id: Some(7),
                user: Some(Box::new(models::User {
                    id: Some(3),
                    login: Some("viewer".into()),
                    ..Default::default()
                })),
                body: Some("Comment".into()),
                ..Default::default()
            },
            models::Comment {
                id: Some(8),
                user: Some(Box::new(models::User {
                    id: Some(4),
                    login: Some("someone-else".into()),
                    ..Default::default()
                })),
                ..Default::default()
            },
        ],
        viewer_id: Some(3),
        has_more: false,
    });

    assert_eq!(page.state, WorkItemState::Closed);
    assert_eq!(page.labels[0].name, "bug");
    assert_eq!(page.comments[0].id, 7);
    assert!(page.comments[0].can_edit);
    assert!(!page.comments[1].can_edit);
}

#[test]
fn issue_editor_exposes_selected_metadata_and_due_date() {
    let selected_label = models::Label {
        id: Some(2),
        name: Some("bug".into()),
        ..Default::default()
    };
    let page = issue_editor_page(IssueEditorData {
        issue: Some(models::Issue {
            title: Some("Title".into()),
            body: Some("Body".into()),
            due_date: Some("2024-02-29T18:00:00Z".into()),
            state: Some("closed".into()),
            labels: Some(vec![selected_label.clone()]),
            milestone: Some(Box::new(models::Milestone {
                id: Some(4),
                title: Some("Current".into()),
                state: Some("closed".into()),
                ..Default::default()
            })),
            ..Default::default()
        }),
        labels: vec![selected_label],
        milestones: vec![models::Milestone {
            id: Some(4),
            title: Some("Current".into()),
            state: Some("closed".into()),
            ..Default::default()
        }],
    });

    assert_eq!((page.title.as_str(), page.body.as_str()), ("Title", "Body"));
    assert_eq!(page.due_date, Some(1_709_164_800));
    assert!(page.closed);
    assert!(page.labels[0].selected);
    assert!(page.milestones[0].selected);

    let new = issue_editor_page(IssueEditorData {
        issue: None,
        labels: Vec::new(),
        milestones: Vec::new(),
    });
    assert!(!new.closed);
}

#[test]
fn builds_sorted_issue_filter_options() {
    let filter = IssueFilter {
        milestone: "v3".into(),
        labels: ["bug".into(), "retired".into()].into(),
        search_text: "login".into(),
    };
    let options = issue_filter_options(
        &[
            models::Label {
                name: Some("critical".into()),
                ..Default::default()
            },
            models::Label {
                name: Some("bug".into()),
                ..Default::default()
            },
        ],
        &[
            models::Milestone {
                title: Some("v2".into()),
                ..Default::default()
            },
            models::Milestone {
                title: Some("v1".into()),
                ..Default::default()
            },
        ],
        &filter,
    );

    assert_eq!(options.labels, ["bug", "critical", "retired"]);
    assert_eq!(options.unavailable_labels, ["retired"]);
    assert_eq!(options.milestones, ["v1", "v2", "v3"]);
    assert_eq!(options.selected_labels, ["bug", "retired"]);
    assert_eq!(options.selected_milestone, "v3");
    assert_eq!(options.search_text, "login");

    let pull_options = pull_filter_options(
        &["v2".into(), "v1".into()],
        &PullFilter {
            milestone: "v3".into(),
            search_text: "review".into(),
        },
    );
    assert_eq!(pull_options.milestones, ["v1", "v2", "v3"]);
    assert_eq!(pull_options.selected_milestone, "v3");
    assert_eq!(pull_options.search_text, "review");
}

#[test]
fn milestone_page_includes_linked_pull_requests() {
    let page = milestone_page(MilestoneDetails {
        milestone: models::Milestone::default(),
        issues: Vec::new(),
        pulls: vec![models::Issue {
            number: Some(7),
            state: Some("closed".into()),
            title: Some("Linked pull".into()),
            repository: Some(Box::new(models::RepositoryMeta {
                name: Some("demo".into()),
                owner: Some("octo".into()),
                ..Default::default()
            })),
            ..Default::default()
        }],
        has_more: false,
    });

    assert!(page.issues.is_empty());
    assert_eq!(page.pulls.len(), 1);
    assert_eq!(page.pulls[0].number, 7);
    assert_eq!(page.pulls[0].state, WorkItemState::Closed);
}

#[test]
fn pull_page_exposes_open_closed_state() {
    let page = pull_page(PullDetails {
        pull: models::PullRequest {
            state: Some("closed".into()),
            ..Default::default()
        },
        comments: Vec::new(),
        files: Vec::new(),
        has_more: false,
    });

    assert_eq!(page.state, WorkItemState::Closed);
}

#[test]
fn milestone_editor_preserves_description_and_optional_due_date() {
    let page = milestone_editor_page(Some(models::Milestone {
        title: Some("Version 1".into()),
        description: Some("Ship it".into()),
        due_on: Some("2024-02-29T12:34:56Z".into()),
        state: Some("closed".into()),
        ..Default::default()
    }));
    assert_eq!(page.title, "Version 1");
    assert_eq!(page.description, "Ship it");
    assert_eq!(page.due_date, Some(1_709_164_800));
    assert!(page.closed);

    let new = milestone_editor_page(None);
    assert_eq!(new.due_date, None);
    assert!(!new.closed);
}

#[test]
fn classifies_repository_files_in_rust() {
    let markdown = repository_file_page("docs/README.md", b"# Hello".to_vec());
    assert_eq!(
        (
            markdown.name.as_str(),
            markdown.kind,
            markdown.language.as_str()
        ),
        ("README.md", RepositoryFileKind::Markdown, "markdown")
    );
    assert_eq!(markdown.text, "# Hello");

    let source = repository_file_page("Sources/App.swift", b"import UIKit".to_vec());
    assert_eq!(
        (source.kind, source.language.as_str()),
        (RepositoryFileKind::Source, "swift")
    );

    let preview = repository_file_page("image.png", vec![0, 159]);
    assert_eq!(preview.kind, RepositoryFileKind::Preview);
    assert_eq!(preview.data, [0, 159]);

    for path in ["manual.pdf", "sound.mp3", "movie.mp4"] {
        assert_eq!(
            repository_file_page(path, Vec::new()).kind,
            RepositoryFileKind::Preview
        );
    }
}
