use std::path::PathBuf;

use async_trait::async_trait;
use dagger_rust::source::RustSource;
use dagger_sdk::Container;

use crate::{
    cli,
    rust_service::architecture::{Architecture, Os},
    rust_workspace, MainAction, PullRequestAction,
};

#[derive(Clone)]
pub struct RustLib {
    client: dagger_sdk::Query,
    base_image: Option<dagger_sdk::Container>,
    source: Option<PathBuf>,
    crates: Vec<String>,
    arch: Option<Architecture>,
    os: Option<Os>,
}

impl RustLib {
    pub fn new(value: dagger_sdk::Query) -> Self {
        Self {
            client: value,
            source: None,
            crates: Vec::new(),
            arch: None,
            os: None,
            base_image: None,
        }
    }

    pub fn with_source(&mut self, path: impl Into<PathBuf>) -> &mut Self {
        self.source = Some(path.into());

        self
    }

    pub fn with_base_image(&mut self, base: dagger_sdk::Container) -> &mut Self {
        self.base_image = Some(base);

        self
    }

    pub fn with_crates(
        &mut self,
        crates: impl IntoIterator<Item = impl Into<String>>,
    ) -> &mut Self {
        self.crates = crates.into_iter().map(|c| c.into()).collect();

        self
    }

    pub async fn with_workspace_crates(&mut self) -> &mut Self {
        if let Ok(Some(file)) = rust_workspace::File::read_file().await {
            if let Some(members) = file.get_workspace_members() {
                return self.with_crates(members);
            }
        }

        self
    }

    pub fn with_arch(&mut self, arch: Architecture) -> &mut Self {
        self.arch = Some(arch);

        self
    }

    pub fn with_os(&mut self, os: Os) -> &mut Self {
        self.os = Some(os);

        self
    }

    fn get_src(&self) -> PathBuf {
        self.source
            .clone()
            .unwrap_or(std::env::current_dir().unwrap())
    }

    pub async fn build_base(&self) -> eyre::Result<Container> {
        let rust_src = RustSource::new(self.client.clone());

        let (src, dep_src) = rust_src
            .get_rust_src(Some(&self.get_src()), self.crates.clone())
            .await?;

        let base_image = self
            .base_image
            .clone()
            .unwrap_or(self.client.container().from("rustlang/rust:nightly"));

        let cache = self.client.cache_volume("rust_target_cache");

        let rust_prebuild = base_image
            .with_workdir("/mnt/src")
            .with_directory("/mnt/src", dep_src)
            .with_exec(vec!["cargo", "build", "--tests", "--workspace"])
            .with_mounted_cache("/mnt/src/target/", cache);

        let incremental_dir = rust_src
            .get_rust_target_src(&self.get_src(), rust_prebuild.clone(), self.crates.clone())
            .await?;

        let rust_with_src = base_image
            .with_workdir("/mnt/src")
            .with_directory(
                "/usr/local/cargo",
                rust_prebuild.directory("/usr/local/cargo"),
            )
            .with_directory("/mnt/src/target", incremental_dir)
            .with_directory("/mnt/src/", src);

        Ok(rust_with_src)
    }

    pub async fn build_test(&self) -> eyre::Result<()> {
        let base = self.build_base().await?;

        base.with_exec(vec!["cargo", "test", "--tests", "--workspace"])
            .sync()
            .await?;

        Ok(())
    }
}

#[async_trait]
impl PullRequestAction for RustLib {
    async fn execute_pull_request(&self, _ctx: &mut cli::Context) -> eyre::Result<()> {
        self.build_test().await?;

        Ok(())
    }
}

#[async_trait]
impl MainAction for RustLib {
    async fn execute_main(&self, _ctx: &mut cli::Context) -> eyre::Result<()> {
        self.build_test().await?;

        Ok(())
    }
}
