use gitea_openapi::apis::notification_api;

use crate::{Client, Error, Page, Result, models, positive, validate_page};

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum NotificationTarget {
    None,
    Repository {
        owner: String,
        repository: String,
    },
    Issue {
        owner: String,
        repository: String,
        number: i64,
    },
    Pull {
        owner: String,
        repository: String,
        number: i64,
    },
    Commit {
        owner: String,
        repository: String,
        sha: String,
    },
}

impl Client {
    pub async fn notifications(
        &self,
        unread: bool,
        page: i32,
        limit: i32,
    ) -> Result<Page<models::NotificationThread>> {
        validate_page(page, limit)?;
        let items = notification_api::notify_get_list(
            &self.configuration(),
            Some(!unread),
            Some(vec![if unread { "unread" } else { "read" }.into()]),
            None,
            None,
            None,
            Some(page),
            Some(limit),
        )
        .await
        .map_err(Error::generated)?;
        Ok(Page::from_items(items, limit))
    }

    pub async fn notification_updates(
        &self,
        since: Option<&str>,
    ) -> Result<Vec<models::NotificationThread>> {
        let mut notifications = Vec::new();
        for page in 1.. {
            let batch = notification_api::notify_get_list(
                &self.configuration(),
                Some(false),
                Some(vec!["unread".into()]),
                None,
                since.map(str::to_owned),
                None,
                Some(page),
                Some(100),
            )
            .await
            .map_err(Error::generated)?;
            let complete = batch.len() < 100;
            notifications.extend(batch);
            if complete {
                break;
            }
        }
        Ok(notifications)
    }

    pub async fn mark_notification_read(&self, id: i64) -> Result<models::NotificationThread> {
        positive(id, "notification ID")?;
        notification_api::notify_read_thread(&self.configuration(), &id.to_string(), Some("read"))
            .await
            .map_err(Error::generated)
    }
}

pub fn target(notification: &models::NotificationThread) -> NotificationTarget {
    let Some(repository) = notification.repository.as_deref() else {
        return NotificationTarget::None;
    };
    let Some(owner) = repository
        .owner
        .as_deref()
        .and_then(|owner| owner.login.clone())
    else {
        return NotificationTarget::None;
    };
    let Some(repository) = repository.name.clone() else {
        return NotificationTarget::None;
    };
    let Some(subject) = notification.subject.as_deref() else {
        return NotificationTarget::Repository { owner, repository };
    };
    let value = subject
        .url
        .as_deref()
        .or(subject.html_url.as_deref())
        .and_then(last_path_component);
    match subject
        .r#type
        .as_deref()
        .unwrap_or_default()
        .to_ascii_lowercase()
        .as_str()
    {
        "issue" => {
            value
                .and_then(|value| value.parse().ok())
                .map_or(NotificationTarget::None, |number| {
                    NotificationTarget::Issue {
                        owner,
                        repository,
                        number,
                    }
                })
        }
        "pull" | "pullrequest" | "pull_request" => value
            .and_then(|value| value.parse().ok())
            .map_or(NotificationTarget::None, |number| {
                NotificationTarget::Pull {
                    owner,
                    repository,
                    number,
                }
            }),
        "commit" => value.map_or(NotificationTarget::None, |sha| NotificationTarget::Commit {
            owner,
            repository,
            sha: sha.into(),
        }),
        "repository" => NotificationTarget::Repository { owner, repository },
        _ => NotificationTarget::None,
    }
}

fn last_path_component(url: &str) -> Option<&str> {
    url.trim_end_matches('/')
        .rsplit('/')
        .next()
        .filter(|value| !value.is_empty())
}

#[cfg(test)]
mod tests {
    use super::*;

    fn notification(kind: &str, url: &str) -> models::NotificationThread {
        models::NotificationThread {
            repository: Some(Box::new(models::Repository {
                name: Some("demo".into()),
                owner: Some(Box::new(models::User {
                    login: Some("octo".into()),
                    ..Default::default()
                })),
                ..Default::default()
            })),
            subject: Some(Box::new(models::NotificationSubject {
                r#type: Some(kind.into()),
                url: Some(url.into()),
                ..Default::default()
            })),
            ..Default::default()
        }
    }

    #[test]
    fn maps_notification_subjects_to_native_destinations() {
        assert_eq!(
            target(&notification(
                "Issue",
                "https://gitea.example/api/v1/repos/octo/demo/issues/42"
            )),
            NotificationTarget::Issue {
                owner: "octo".into(),
                repository: "demo".into(),
                number: 42,
            }
        );
        assert_eq!(
            target(&notification(
                "Pull",
                "https://gitea.example/api/v1/repos/octo/demo/pulls/7"
            )),
            NotificationTarget::Pull {
                owner: "octo".into(),
                repository: "demo".into(),
                number: 7,
            }
        );
        assert_eq!(
            target(&notification(
                "Commit",
                "https://gitea.example/api/v1/repos/octo/demo/git/commits/deadbeef"
            )),
            NotificationTarget::Commit {
                owner: "octo".into(),
                repository: "demo".into(),
                sha: "deadbeef".into(),
            }
        );
    }
}
