use thiserror::Error;

pub type Result<T> = std::result::Result<T, CjvError>;

#[derive(Error, Debug)]
pub enum CjvError {
    #[error("Network error: {0}")]
    Network(#[from] reqwest::Error),

    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("Serialization error: {0}")]
    Serde(#[from] serde_json::Error),

    #[error("TOML parsing error: {0}")]
    Toml(#[from] toml::de::Error),

    #[error("TOML serialization error: {0}")]
    TomlSer(#[from] toml::ser::Error),

    #[error("Zip error: {0}")]
    Zip(#[from] zip::result::ZipError),

    #[error("Not found: {0}")]
    NotFound(String),

    #[error("Already exists: {0}")]
    AlreadyExists(String),

    #[error("Invalid argument: {0}")]
    InvalidArgument(String),

    #[error("Toolchain '{0}' not installed")]
    ToolchainNotInstalled(String),

    #[error("Unsupported platform: {0}")]
    UnsupportedPlatform(String),

    #[error("Checksum mismatch: expected {expected}, got {actual}")]
    ChecksumMismatch { expected: String, actual: String },

    #[error("Download failed: {0}")]
    DownloadFailed(String),

    #[error("Extraction failed: {0}")]
    ExtractionFailed(String),

    #[error("Proxy error: {0}")]
    Proxy(String),

    #[error("{0}")]
    Other(String),
}

impl CjvError {
    pub fn exit_code(&self) -> u8 {
        match self {
            CjvError::NotFound(_) => 2,
            CjvError::InvalidArgument(_) => 3,
            CjvError::ToolchainNotInstalled(_) => 4,
            _ => 1,
        }
    }
}

impl From<&str> for CjvError {
    fn from(s: &str) -> Self {
        CjvError::Other(s.to_string())
    }
}

impl From<String> for CjvError {
    fn from(s: String) -> Self {
        CjvError::Other(s)
    }
}

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

    #[test]
    fn stable_exit_codes_for_common_failures() {
        assert_eq!(CjvError::NotFound("x".to_string()).exit_code(), 2);
        assert_eq!(CjvError::InvalidArgument("x".to_string()).exit_code(), 3);
        assert_eq!(
            CjvError::ToolchainNotInstalled("x".to_string()).exit_code(),
            4
        );
        assert_eq!(CjvError::Other("x".to_string()).exit_code(), 1);
    }
}