use gotcha_gitea::{
    Client, DEFAULT_PAGE_SIZE as PAGE_SIZE, IssueQuery, RepositoryId, api_date, diff, models,
};

use crate::{
    domain::{
        HistoryCommit, HomeData, IssueDetails, IssueDraft, IssueEditorData, IssueFilter,
        MilestoneDetails, MilestoneDraft, Page, PullDetails, RepositoryData, Server,
    },
    presentation::compact_date,
};

pub async fn load_repositories(server: &Server, page: i32) -> Result<Page<RepositoryData>, String> {
    let page = client(server)?
        .owned_repositories_page(page, PAGE_SIZE)
        .await
        .map_err(message)?;
    Ok(Page {
        has_more: page.has_more,
        items: page
            .items
            .into_iter()
            .filter_map(|repository| {
                Some(RepositoryData {
                    owner: repository.owner?.login?,
                    name: repository.name?,
                    description: repository
                        .description
                        .filter(|text| !text.is_empty())
                        .unwrap_or_else(|| "No description".into()),
                    language: repository
                        .language
                        .filter(|text| !text.is_empty())
                        .unwrap_or_else(|| "Unknown language".into()),
                    open_issues: repository.open_issues_count.unwrap_or_default(),
                    updated: compact_date(repository.updated_at.as_deref()),
                    default_branch: repository.default_branch.unwrap_or_else(|| "main".into()),
                })
            })
            .collect(),
    })
}

pub async fn load_notifications(
    server: &Server,
    unread: bool,
    page: i32,
) -> Result<Page<models::NotificationThread>, String> {
    client(server)?
        .notifications(unread, page, PAGE_SIZE)
        .await
        .map_err(message)
}

pub async fn load_notification_updates(
    server: &Server,
    since: Option<&str>,
) -> Result<Vec<models::NotificationThread>, String> {
    client(server)?
        .notification_updates(since)
        .await
        .map_err(message)
}

pub async fn mark_notification_read(server: &Server, id: i64) -> Result<(), String> {
    client(server)?
        .mark_notification_read(id)
        .await
        .map(|_| ())
        .map_err(message)
}

pub async fn load_issues(
    server: &Server,
    owner: &str,
    repository: &str,
    status: &str,
    filter: &IssueFilter,
    page: i32,
) -> Result<Page<models::Issue>, String> {
    client(server)?
        .issues(
            &scope(owner, repository)?,
            &IssueQuery {
                state: status.into(),
                labels: (!filter.labels.is_empty())
                    .then(|| filter.labels.iter().cloned().collect::<Vec<_>>().join(",")),
                milestones: (!filter.milestone.is_empty()).then(|| filter.milestone.clone()),
                keyword: (!filter.search_text.is_empty()).then(|| filter.search_text.clone()),
                page,
                limit: PAGE_SIZE,
                ..Default::default()
            },
        )
        .await
        .map_err(message)
}

pub async fn load_milestones_page(
    server: &Server,
    owner: &str,
    repository: &str,
    page: i32,
) -> Result<Page<models::Milestone>, String> {
    client(server)?
        .milestones_page(&scope(owner, repository)?, page, PAGE_SIZE)
        .await
        .map_err(message)
}

pub async fn load_labels(
    server: &Server,
    owner: &str,
    repository: &str,
) -> Result<Vec<models::Label>, String> {
    client(server)?
        .labels(&scope(owner, repository)?)
        .await
        .map_err(message)
}

pub async fn load_milestones(
    server: &Server,
    owner: &str,
    repository: &str,
) -> Result<Vec<models::Milestone>, String> {
    client(server)?
        .milestones(&scope(owner, repository)?)
        .await
        .map_err(message)
}

pub async fn load_milestone(
    server: &Server,
    owner: &str,
    repository: &str,
    id: i64,
    page: i32,
) -> Result<MilestoneDetails, String> {
    client(server)?
        .milestone_details(&scope(owner, repository)?, id, page)
        .await
        .map_err(message)
}

pub async fn load_milestone_editor(
    server: &Server,
    owner: &str,
    repository: &str,
    id: i64,
) -> Result<models::Milestone, String> {
    client(server)?
        .milestone(&scope(owner, repository)?, id)
        .await
        .map_err(message)
}

pub async fn save_milestone(
    server: &Server,
    owner: &str,
    repository: &str,
    id: Option<i64>,
    draft: MilestoneDraft,
) -> Result<models::Milestone, String> {
    client(server)?
        .save_milestone(
            &scope(owner, repository)?,
            id,
            gotcha_gitea::MilestoneDraft {
                title: draft.title,
                description: draft.description,
                due_on: draft.due_date.map(api_date),
                state: if draft.closed { "closed" } else { "open" }.into(),
            },
        )
        .await
        .map_err(message)
}

pub async fn set_milestone_closed(
    server: &Server,
    owner: &str,
    repository: &str,
    id: i64,
    closed: bool,
) -> Result<(), String> {
    client(server)?
        .set_milestone_closed(&scope(owner, repository)?, id, closed)
        .await
        .map(|_| ())
        .map_err(message)
}

pub async fn delete_milestone(
    server: &Server,
    owner: &str,
    repository: &str,
    id: i64,
) -> Result<(), String> {
    client(server)?
        .delete_milestone(&scope(owner, repository)?, &id.to_string())
        .await
        .map_err(message)
}

pub async fn load_pulls(
    server: &Server,
    status: &str,
    milestone: &str,
    search_text: &str,
    page: i32,
) -> Result<Page<models::Issue>, String> {
    client(server)?
        .search_pulls(
            status,
            (!milestone.is_empty()).then_some(milestone),
            (!search_text.is_empty()).then_some(search_text),
            page,
            PAGE_SIZE,
        )
        .await
        .map_err(message)
}

pub async fn load_pull_milestones(server: &Server) -> Result<Vec<String>, String> {
    client(server)?.pull_milestones().await.map_err(message)
}

pub async fn load_branches(
    server: &Server,
    owner: &str,
    repository: &str,
    default_branch: &str,
) -> Result<Vec<String>, String> {
    client(server)?
        .branches(&scope(owner, repository)?, default_branch)
        .await
        .map_err(message)
}

pub async fn load_repository_contents(
    server: &Server,
    owner: &str,
    repository: &str,
    path: &str,
) -> Result<Vec<models::ContentsResponse>, String> {
    client(server)?
        .repository_contents(&scope(owner, repository)?, path)
        .await
        .map_err(message)
}

pub async fn load_repository_file(
    server: &Server,
    owner: &str,
    repository: &str,
    path: &str,
) -> Result<Vec<u8>, String> {
    client(server)?
        .repository_file(&scope(owner, repository)?, path)
        .await
        .map_err(message)
}

pub async fn load_branch_commits(
    server: &Server,
    owner: &str,
    repository: &str,
    branch: &str,
    path: Option<&str>,
    pages: u32,
) -> Result<Page<HistoryCommit>, String> {
    client(server)?
        .branch_history(&scope(owner, repository)?, branch, path, pages)
        .await
        .map_err(message)
}

pub async fn load_all_commits(
    server: &Server,
    owner: &str,
    repository: &str,
    branches: &[String],
    path: Option<&str>,
    pages: u32,
) -> Result<Page<HistoryCommit>, String> {
    client(server)?
        .all_history(&scope(owner, repository)?, branches, path, pages)
        .await
        .map_err(message)
}

pub async fn load_issue(
    server: &Server,
    owner: &str,
    repository: &str,
    number: i64,
    _page: i32,
) -> Result<IssueDetails, String> {
    client(server)?
        .issue_details(&scope(owner, repository)?, number)
        .await
        .map_err(message)
}

pub async fn save_issue_comment(
    server: &Server,
    owner: &str,
    repository: &str,
    number: i64,
    comment_id: Option<i64>,
    body: String,
) -> Result<(), String> {
    client(server)?
        .save_issue_comment(&scope(owner, repository)?, number, comment_id, body)
        .await
        .map(|_| ())
        .map_err(message)
}

pub async fn load_issue_editor(
    server: &Server,
    owner: &str,
    repository: &str,
    number: Option<i64>,
) -> Result<IssueEditorData, String> {
    client(server)?
        .issue_editor(&scope(owner, repository)?, number)
        .await
        .map_err(message)
}

pub async fn create_issue(
    server: &Server,
    owner: &str,
    repository: &str,
    draft: IssueDraft,
) -> Result<models::Issue, String> {
    client(server)?
        .create_issue_draft(&scope(owner, repository)?, shared_issue_draft(draft))
        .await
        .map_err(message)
}

pub async fn edit_issue(
    server: &Server,
    owner: &str,
    repository: &str,
    number: i64,
    draft: IssueDraft,
) -> Result<models::Issue, String> {
    client(server)?
        .edit_issue_draft(
            &scope(owner, repository)?,
            number,
            shared_issue_draft(draft),
        )
        .await
        .map_err(message)
}

pub async fn set_issue_closed(
    server: &Server,
    owner: &str,
    repository: &str,
    number: i64,
    closed: bool,
) -> Result<(), String> {
    client(server)?
        .set_issue_state(
            &scope(owner, repository)?,
            &[number],
            if closed { "closed" } else { "open" },
        )
        .await
        .map(|_| ())
        .map_err(message)
}

pub async fn delete_issue(
    server: &Server,
    owner: &str,
    repository: &str,
    number: i64,
) -> Result<(), String> {
    client(server)?
        .delete_issue(&scope(owner, repository)?, number)
        .await
        .map_err(message)
}

pub async fn load_pull(
    server: &Server,
    owner: &str,
    repository: &str,
    number: i64,
    page: i32,
) -> Result<PullDetails, String> {
    client(server)?
        .pull_details(&scope(owner, repository)?, number, page)
        .await
        .map_err(message)
}

pub async fn load_commit(
    server: &Server,
    owner: &str,
    repository: &str,
    sha: &str,
) -> Result<models::Commit, String> {
    client(server)?
        .commit(&scope(owner, repository)?, sha)
        .await
        .map_err(message)
}

pub async fn load_commit_diff(
    server: &Server,
    owner: &str,
    repository: &str,
    sha: &str,
    path: &str,
) -> Result<diff::Parsed, String> {
    let client = client(server)?;
    let diff = client
        .commit_diff(&scope(owner, repository)?, sha)
        .await
        .map_err(message)?;
    Ok(diff::parse_file(&diff, path))
}

pub async fn load_pull_diff(
    server: &Server,
    owner: &str,
    repository: &str,
    number: i64,
    path: &str,
) -> Result<diff::Parsed, String> {
    let client = client(server)?;
    let diff = client
        .pull_diff(&scope(owner, repository)?, number)
        .await
        .map_err(message)?;
    Ok(diff::parse_file(&diff, path))
}

pub async fn load_home(
    server: &Server,
    page: i32,
    filter: gotcha_gitea::ActivityFilter,
) -> Result<HomeData, String> {
    client(server)?.home(page, filter).await.map_err(message)
}

pub async fn load_activities(server: &Server, page: i32) -> Result<Page<models::Activity>, String> {
    client(server)?
        .activities(page, gotcha_gitea::ActivityFilter::All)
        .await
        .map_err(message)
}

pub async fn load_server_activities(
    pager: &mut gotcha_gitea::ServerActivityPager,
) -> Result<Page<models::Activity>, String> {
    pager.next_page().await.map_err(message)
}

pub async fn server_activity_pager(
    server: &Server,
) -> Result<gotcha_gitea::ServerActivityPager, String> {
    client(server)?
        .server_activity_pager()
        .await
        .map_err(message)
}

fn client(server: &Server) -> Result<Client, String> {
    Client::with_provider(&server.url, Some(&server.token), server.provider).map_err(message)
}

fn scope(owner: &str, repository: &str) -> Result<RepositoryId, String> {
    RepositoryId::new(owner, repository).map_err(message)
}

fn shared_issue_draft(draft: IssueDraft) -> gotcha_gitea::IssueDraft {
    gotcha_gitea::IssueDraft {
        title: draft.title,
        body: draft.body,
        label_ids: draft.label_ids,
        milestone_id: draft.milestone_id,
        due_date: draft.due_date.map(api_date),
        closed: draft.closed,
    }
}

fn message(error: impl std::fmt::Display) -> String {
    error.to_string()
}
