use std::path::PathBuf;

use serde::{Deserialize, Serialize};

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

/// Create a symlink.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Symlink {
    target: PathBuf,
    link_name: PathBuf,
}

impl Symlink {
    /// Create a new [`Symlink`].
    pub fn new(target: impl Into<PathBuf>, link_name: impl Into<PathBuf>) -> Self {
        Self {
            target: target.into(),
            link_name: link_name.into(),
        }
    }
}

impl ActionImpl for Symlink {
    fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
        if self.link_name.exists() {
            context
                .runlog()
                .trace("symlink exists", self.link_name.to_string_lossy());
        }
        if self.target.exists() {
            context
                .runlog()
                .trace("symlink target exists", self.target.to_string_lossy());
        }
        std::os::unix::fs::symlink(&self.target, &self.link_name).map_err(|source| {
            SymlinkError::Create {
                link_name: self.link_name.clone(),
                target: self.target.clone(),
                source,
            }
        })?;
        Ok(())
    }
}

/// Errors from `Symlink` action.
#[derive(Debug, thiserror::Error)]
pub enum SymlinkError {
    /// Can't create symlink.
    #[error("failed to create symlink {link_name} -> {target}")]
    Create {
        /// Name of symlink.
        link_name: PathBuf,

        /// Symlink target.
        target: PathBuf,

        /// Underlying error.
        source: std::io::Error,
    },
}

impl From<SymlinkError> for ActionError {
    fn from(err: SymlinkError) -> Self {
        ActionError::Symlink(err)
    }
}
