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

use serde::{Deserialize, Serialize};

use crate::logfile::{AdminLog, LogError};

/// Configuration file for `radicle-native-ci`.
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
    /// Directory where per-run directories are stored. Each run gets
    /// its own dedicated subdirectory.
    pub state: PathBuf,

    /// File where native CI should write a log.
    pub log: PathBuf,

    /// Optional base URL to information about each run. The run ID is
    /// appended, with a slash if needed.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub base_url: Option<String>,
}

impl Config {
    pub fn load_via_env() -> Result<Self, ConfigError> {
        const ENV: &str = "RADICLE_NATIVE_CI";
        let filename = std::env::var(ENV).map_err(|e| ConfigError::GetEnv(ENV, e))?;
        let filename = Path::new(&filename);
        let config = Config::read(filename)?;
        Ok(config)
    }

    pub fn open_log(&self) -> Result<AdminLog, ConfigError> {
        Ok(AdminLog::open(&self.log)?)
    }

    /// Read configuration specification from a file.
    pub fn read(filename: &Path) -> Result<Self, ConfigError> {
        let file = std::fs::File::open(filename)
            .map_err(|e| ConfigError::ReadConfig(filename.into(), e))?;
        serde_norway::from_reader(&file).map_err(|e| ConfigError::ParseConfig(filename.into(), e))
    }

    /// Return configuration serialized to JSON.
    pub fn as_json(&self) -> String {
        // We don't check the result: we know a configuration can
        // always be serialized to JSON.
        serde_json::to_string_pretty(self).unwrap()
    }
}

#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
    #[error("failed to read configuration file {0}")]
    ReadConfig(PathBuf, #[source] std::io::Error),

    #[error("failed to parse configuration file as YAML: {0}")]
    ParseConfig(PathBuf, #[source] serde_norway::Error),

    #[error("failed to get environment variable {0}")]
    GetEnv(&'static str, #[source] std::env::VarError),

    #[error(transparent)]
    Log(#[from] LogError),
}
