use crate::*;

#[uniffi::export(async_runtime = "tokio")]
impl GotchaCore {
    pub async fn issues(
        &self,
        owner: String,
        repository: String,
        page: u32,
    ) -> Result<IssueListPage, GotchaError> {
        let server = self.server()?;
        let (status, filter) = {
            let state = self.state.lock().unwrap();
            (
                state.preferences.issue_status.clone(),
                state
                    .preferences
                    .issue_filters
                    .get(&repository_key(&server.url, &owner, &repository))
                    .cloned()
                    .unwrap_or_default(),
            )
        };
        let page = load_issues(
            &server,
            &owner,
            &repository,
            &status,
            &filter,
            valid_page(page)?,
        )
        .await?;
        Ok(IssueListPage {
            rows: issue_rows(&page.items),
            has_more: page.has_more,
        })
    }

    pub async fn issue_filters(
        &self,
        owner: String,
        repository: String,
    ) -> Result<IssueFilterOptions, GotchaError> {
        let server = self.server()?;
        let filter = self
            .state
            .lock()
            .unwrap()
            .preferences
            .issue_filters
            .get(&repository_key(&server.url, &owner, &repository))
            .cloned()
            .unwrap_or_default();
        let (labels, milestones) = tokio::try_join!(
            load_labels(&server, &owner, &repository),
            load_milestones(&server, &owner, &repository)
        )?;
        Ok(issue_filter_options(&labels, &milestones, &filter))
    }

    pub fn issue_filters_active(
        &self,
        owner: String,
        repository: String,
    ) -> Result<bool, GotchaError> {
        let server = self.server()?;
        let state = self.state.lock().unwrap();
        let filter = state
            .preferences
            .issue_filters
            .get(&repository_key(
                &server.url,
                owner.trim(),
                repository.trim(),
            ))
            .cloned()
            .unwrap_or_default();
        Ok(filter.is_active(&state.preferences.issue_status))
    }

    pub fn set_issue_filters(
        &self,
        owner: String,
        repository: String,
        milestone: String,
        labels: Vec<String>,
        search_text: String,
    ) -> Result<(), GotchaError> {
        let owner = owner.trim();
        let repository = repository.trim();
        if owner.is_empty() || repository.is_empty() {
            return Err("Select a repository first.".into());
        }
        let filter = IssueFilter {
            milestone,
            labels: labels
                .into_iter()
                .filter(|label| !label.is_empty())
                .collect(),
            search_text: search_text.trim().into(),
        };
        let mut state = self.state.lock().unwrap();
        let server = state
            .active_server
            .and_then(|index| state.preferences.servers.get(index))
            .ok_or("Select a server first.")?;
        let key = repository_key(&server.url, owner, repository);
        if filter == IssueFilter::default() {
            state.preferences.issue_filters.remove(&key);
        } else {
            state.preferences.issue_filters.insert(key, filter);
        }
        save_preferences(&state.preferences).map_err(Into::into)
    }

    pub fn clear_issue_filters(
        &self,
        owner: String,
        repository: String,
    ) -> Result<(), GotchaError> {
        let owner = owner.trim();
        let repository = repository.trim();
        if owner.is_empty() || repository.is_empty() {
            return Err("Select a repository first.".into());
        }
        let mut state = self.state.lock().unwrap();
        let server = state
            .active_server
            .and_then(|index| state.preferences.servers.get(index))
            .ok_or("Select a server first.")?;
        let key = repository_key(&server.url, owner, repository);
        state.preferences.issue_status = "open".into();
        state.preferences.issue_filters.remove(&key);
        save_preferences(&state.preferences).map_err(Into::into)
    }

    pub async fn issue(
        &self,
        owner: String,
        repository: String,
        number: i64,
        page: u32,
    ) -> Result<IssuePage, GotchaError> {
        Ok(issue_page(
            load_issue(
                &self.server()?,
                &owner,
                &repository,
                number,
                valid_page(page)?,
            )
            .await?,
        ))
    }

    pub async fn save_issue_comment(
        &self,
        owner: String,
        repository: String,
        number: i64,
        comment_id: Option<i64>,
        body: String,
    ) -> Result<(), GotchaError> {
        let (owner, repository) = validate_repository(&owner, &repository)?;
        if number <= 0 || comment_id.is_some_and(|id| id <= 0) {
            return Err("Invalid issue comment selection.".into());
        }
        if body.trim().is_empty() {
            return Err("Enter a comment.".into());
        }
        save_issue_comment(&self.server()?, owner, repository, number, comment_id, body).await?;
        Ok(())
    }

    pub async fn issue_editor(
        &self,
        owner: String,
        repository: String,
        number: Option<i64>,
    ) -> Result<IssueEditorPage, GotchaError> {
        let (owner, repository) = validate_repository(&owner, &repository)?;
        if number.is_some_and(|number| number <= 0) {
            return Err("Invalid issue number.".into());
        }
        Ok(issue_editor_page(
            load_issue_editor(&self.server()?, owner, repository, number).await?,
        ))
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn save_issue(
        &self,
        owner: String,
        repository: String,
        number: Option<i64>,
        title: String,
        body: String,
        label_ids: Vec<i64>,
        milestone_id: Option<i64>,
        due_date: Option<i64>,
        closed: bool,
    ) -> Result<i64, GotchaError> {
        let (owner, repository) = validate_repository(&owner, &repository)?;
        let title = title.trim();
        if title.is_empty() {
            return Err("Enter an issue title.".into());
        }
        if number.is_some_and(|number| number <= 0)
            || milestone_id.is_some_and(|id| id <= 0)
            || label_ids.iter().any(|id| *id <= 0)
        {
            return Err("Invalid issue editor selection.".into());
        }
        let label_ids: Vec<_> = label_ids
            .into_iter()
            .collect::<std::collections::BTreeSet<_>>()
            .into_iter()
            .collect();
        let draft = IssueDraft {
            title: title.into(),
            body,
            label_ids,
            milestone_id,
            due_date,
            closed,
        };
        let server = self.server()?;
        let issue = match number {
            Some(number) => edit_issue(&server, owner, repository, number, draft).await?,
            None => create_issue(&server, owner, repository, draft).await?,
        };
        issue
            .number
            .ok_or_else(|| "The saved issue has no number.".into())
    }

    pub async fn set_issue_closed(
        &self,
        owner: String,
        repository: String,
        number: i64,
        closed: bool,
    ) -> Result<(), GotchaError> {
        let (owner, repository) = validate_repository(&owner, &repository)?;
        if number <= 0 {
            return Err("Invalid issue number.".into());
        }
        set_issue_closed(&self.server()?, owner, repository, number, closed).await?;
        Ok(())
    }

    pub async fn delete_issue(
        &self,
        owner: String,
        repository: String,
        number: i64,
    ) -> Result<(), GotchaError> {
        let (owner, repository) = validate_repository(&owner, &repository)?;
        if number <= 0 {
            return Err("Invalid issue number.".into());
        }
        delete_issue(&self.server()?, owner, repository, number).await?;
        Ok(())
    }
}
