use efdn_telem::canonical::service::{ServiceName, ServiceVersion};
use serde::Serialize;

/// Static build metadata accepted by the gateway.
pub trait GatewayBuildMeta {
    /// Cargo package name.
    fn package_name(&self) -> &'static str;
    /// Cargo package version.
    fn package_version(&self) -> &'static str;
    /// Rust crate name.
    fn crate_name(&self) -> &'static str;
    /// Binary name when available.
    fn binary_name(&self) -> Option<&'static str>;
    /// Full git SHA.
    fn git_sha(&self) -> &'static str;
    /// Short git SHA.
    fn git_short_sha(&self) -> &'static str;
    /// Git describe output.
    fn git_describe(&self) -> &'static str;
    /// Returns true when the working tree was dirty.
    fn git_dirty(&self) -> bool;
    /// Rust compiler version.
    fn rustc_version(&self) -> &'static str;
    /// Build timestamp.
    fn build_timestamp(&self) -> &'static str;
}

/// Gateway service identity input
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GatewayServiceInfo {
    name: ServiceName,
    version: ServiceVersion,
    build: Option<GatewayBuildInfo>,
}

impl GatewayServiceInfo {
    /// Creates service identity without build metadata.
    pub fn new(name: ServiceName, version: ServiceVersion) -> Self {
        Self {
            name,
            version,
            build: None,
        }
    }

    #[doc(hidden)]
    pub fn from_build_meta(
        name: ServiceName,
        version: ServiceVersion,
        meta: impl GatewayBuildMeta,
    ) -> Self {
        Self {
            name,
            version,
            build: Some(GatewayBuildInfo::from_meta(&meta)),
        }
    }

    /// Returns the service name.
    pub fn name(&self) -> &ServiceName {
        &self.name
    }

    /// Returns the service version.
    pub fn version(&self) -> &ServiceVersion {
        &self.version
    }

    /// Returns build metadata when provided.
    pub fn build(&self) -> Option<&GatewayBuildInfo> {
        self.build.as_ref()
    }
}

/// Build metadata attached to `/version`
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct GatewayBuildInfo {
    package: GatewayBuildPackage,
    target: GatewayBuildTarget,
    git: GatewayBuildGit,
    rustc: GatewayBuildRustc,
    stamp: GatewayBuildStamp,
}

impl GatewayBuildInfo {
    fn from_meta(meta: &impl GatewayBuildMeta) -> Self {
        Self {
            package: GatewayBuildPackage {
                name: meta.package_name(),
                version: meta.package_version(),
            },
            target: GatewayBuildTarget {
                crate_name: meta.crate_name(),
                binary_name: meta.binary_name(),
            },
            git: GatewayBuildGit {
                sha: meta.git_sha(),
                short_sha: meta.git_short_sha(),
                describe: meta.git_describe(),
                dirty: meta.git_dirty(),
            },
            rustc: GatewayBuildRustc {
                version: meta.rustc_version(),
            },
            stamp: GatewayBuildStamp {
                timestamp: meta.build_timestamp(),
            },
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
struct GatewayBuildPackage {
    name: &'static str,
    version: &'static str,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
struct GatewayBuildTarget {
    crate_name: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    binary_name: Option<&'static str>,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
struct GatewayBuildGit {
    sha: &'static str,
    short_sha: &'static str,
    describe: &'static str,
    dirty: bool,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
struct GatewayBuildRustc {
    version: &'static str,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
struct GatewayBuildStamp {
    timestamp: &'static str,
}
