use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};

use crate::{
    action::{ActionError, Context},
    action_impl::{spawn, ActionImpl},
};

/// Upload build artifacts using `rsync` to configured server.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Rsync {
    artifacts: PathBuf,
    rsync_target: Option<String>,
}

impl Rsync {
    /// Create a new `Rsync` action.
    pub fn new<P: AsRef<Path>>(artifacts: P, rsync_target: Option<String>) -> Self {
        Self {
            artifacts: artifacts.as_ref().to_path_buf(),
            rsync_target,
        }
    }
}

impl ActionImpl for Rsync {
    fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
        if let Some(target) = &self.rsync_target {
            let src = context.artifacts_dir().join(&self.artifacts);
            let src = format!("{}/.", src.display());
            let target = format!("{target}/.");
            let r = spawn(
                context,
                &[
                    "rsync", "-av", "--del", "--stats", "-essh -v", &src, &target,
                ],
            );
            context.runlog().debug(
                crate::runlog::RunLogSource::PostPlan,
                format!("rsync result: {r:#?}"),
            );
            r?;
        } else {
            context.runlog().action_skipped(
                crate::runlog::RunLogSource::PostPlan,
                "rsync".to_string(),
                "rsync target is not configured",
            );
        }
        Ok(())
    }
}

/// Errors from Rsync action.
#[derive(Debug, thiserror::Error)]
pub enum RsyncError {
    /// `rsync` failed.
    #[error("rsync failed to synchronize {0} to {1}:\n{2}")]
    Rsync(PathBuf, String, String),

    /// Can't run program.
    #[error("failed to run program {0}")]
    Execute(&'static str, #[source] clingwrap::runner::CommandError),
}
