use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Os {
    Macos,
    Linux,
    Windows,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Arch {
    #[serde(rename = "arm64")]
    Arm64,
    #[serde(rename = "x86_64")]
    X86_64,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Platform {
    pub os: Os,
    pub arch: Arch,
}

impl Os {
    pub fn as_str(self) -> &'static str {
        match self {
            Os::Macos => "macos",
            Os::Linux => "linux",
            Os::Windows => "windows",
        }
    }

    pub fn from_target(value: &str) -> Option<Self> {
        match value {
            "macos" => Some(Os::Macos),
            "linux" => Some(Os::Linux),
            "windows" => Some(Os::Windows),
            _ => None,
        }
    }
}

impl Arch {
    pub fn as_str(self) -> &'static str {
        match self {
            Arch::Arm64 => "arm64",
            Arch::X86_64 => "x86_64",
        }
    }

    pub fn from_target(value: &str) -> Option<Self> {
        match value {
            "aarch64" | "arm64" => Some(Arch::Arm64),
            "x86_64" | "amd64" => Some(Arch::X86_64),
            _ => None,
        }
    }
}

impl Platform {
    pub fn detect() -> Option<Self> {
        let os = Os::from_target(std::env::consts::OS)?;
        let arch = Arch::from_target(std::env::consts::ARCH)?;
        Some(Self { os, arch })
    }

    pub fn slug(self) -> String {
        format!("{}-{}", self.os.as_str(), self.arch.as_str())
    }

    pub fn is_macos(self) -> bool {
        matches!(self.os, Os::Macos)
    }

    pub fn is_linux(self) -> bool {
        matches!(self.os, Os::Linux)
    }

    pub fn is_windows(self) -> bool {
        matches!(self.os, Os::Windows)
    }
}

pub fn download_url(base_url: &str, product: &str, channel: &str, platform: &Platform) -> String {
    let base = base_url.trim_end_matches('/');
    format!(
        "{base}/api/v1/downloads/{product}/{channel}/{slug}",
        slug = platform.slug()
    )
}

pub fn pick_download_url(
    base_url: &str,
    product: &str,
    channel: &str,
    fallback: Option<Platform>,
) -> Option<String> {
    let platform = Platform::detect().or(fallback)?;
    Some(download_url(base_url, product, channel, &platform))
}
