use std::sync::Arc;

use async_trait::async_trait;
use tokio::sync::Mutex;

pub struct CuddleCI {
    pr_action: Vec<Arc<Mutex<dyn PullRequestAction + Send + Sync>>>,
    main_action: Vec<Arc<Mutex<dyn MainAction + Send + Sync>>>,
    release_action: Vec<Arc<Mutex<dyn ReleaseAction + Send + Sync>>>,
}

impl CuddleCI {
    pub fn new(
        pr: Arc<Mutex<dyn PullRequestAction + Send + Sync>>,
        main: Arc<Mutex<dyn MainAction + Send + Sync>>,
        release: Arc<Mutex<dyn ReleaseAction + Send + Sync>>,
    ) -> Self {
        Self {
            pr_action: vec![pr],
            main_action: vec![main],
            release_action: vec![release],
        }
    }

    pub fn with_pull_request(
        &mut self,
        pr: Arc<Mutex<dyn PullRequestAction + Send + Sync>>,
    ) -> &mut Self {
        self.pr_action.push(pr);

        self
    }

    pub fn with_main(&mut self, main: Arc<Mutex<dyn MainAction + Send + Sync>>) -> &mut Self {
        self.main_action.push(main);

        self
    }

    pub fn with_release(
        &mut self,
        release: Arc<Mutex<dyn ReleaseAction + Send + Sync>>,
    ) -> &mut Self {
        self.release_action.push(release);

        self
    }

    pub async fn execute(
        &mut self,
        args: impl IntoIterator<Item = impl Into<String>>,
    ) -> eyre::Result<()> {
        let matches = clap::Command::new("cuddle-ci")
            .about("is a wrapper around common CI actions")
            .subcommand(clap::Command::new("pr"))
            .subcommand(clap::Command::new("main"))
            .subcommand(clap::Command::new("release"))
            .subcommand_required(true)
            .try_get_matches_from(args.into_iter().map(|a| a.into()).collect::<Vec<String>>())?;

        match matches.subcommand() {
            Some((name, args)) => match (name, args) {
                ("pr", _args) => {
                    eprintln!("starting pr validate");
                    for pr_action in self.pr_action.iter() {
                        pr_action.lock().await.execute_pull_request().await?;
                    }
                    eprintln!("finished pr validate");
                }
                ("main", _args) => {
                    eprintln!("starting main validate");
                    for main_action in self.main_action.iter() {
                        main_action.lock().await.execute_main().await?;
                    }
                    eprintln!("finished main validate");
                }
                ("release", _args) => {
                    eprintln!("starting release validate");
                    for release_action in self.release_action.iter() {
                        release_action.lock().await.execute_release().await?;
                    }
                    eprintln!("finished release validate");
                }
                (command_name, _) => {
                    eyre::bail!("command is not recognized: {}", command_name)
                }
            },
            None => eyre::bail!("command required a subcommand [pr, main, release] etc."),
        }

        Ok(())
    }
}

impl Default for CuddleCI {
    fn default() -> Self {
        Self::new(
            Arc::new(Mutex::new(DefaultPullRequestAction {})),
            Arc::new(Mutex::new(DefaultMainAction {})),
            Arc::new(Mutex::new(DefaultReleaseAction {})),
        )
    }
}

#[async_trait]
pub trait PullRequestAction {
    async fn execute_pull_request(&self) -> eyre::Result<()> {
        eprintln!("validate pull request: noop");
        Ok(())
    }
}

pub struct DefaultPullRequestAction {}
#[async_trait]
impl PullRequestAction for DefaultPullRequestAction {}

#[async_trait]
pub trait MainAction {
    async fn execute_main(&self) -> eyre::Result<()>;
}

pub struct DefaultMainAction {}
#[async_trait]
impl MainAction for DefaultMainAction {
    async fn execute_main(&self) -> eyre::Result<()> {
        Ok(())
    }
}

#[async_trait]
pub trait ReleaseAction {
    async fn execute_release(&self) -> eyre::Result<()> {
        eprintln!("validate release: noop");

        Ok(())
    }
}

pub struct DefaultReleaseAction {}
#[async_trait]
impl ReleaseAction for DefaultReleaseAction {}

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

    #[tokio::test]
    async fn test_can_call_default() -> eyre::Result<()> {
        CuddleCI::default().execute(["cuddle-ci", "pr"]).await?;

        Ok(())
    }

    #[tokio::test]
    async fn test_fails_on_no_command() -> eyre::Result<()> {
        let res = CuddleCI::default().execute(["cuddle-ci"]).await;

        assert!(res.is_err());

        Ok(())
    }

    #[tokio::test]
    async fn test_fails_on_wrong_command() -> eyre::Result<()> {
        let res = CuddleCI::default()
            .execute(["cuddle-ci", "something"])
            .await;

        assert!(res.is_err());

        Ok(())
    }
}
