// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0

// TODO: at a later date this crate should probably also expose `ContractBuildInformation`
// and be used by our smart contracts

use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};

#[derive(Debug)]
pub struct BinaryBuildInformation {
    /// Provides the name of the binary, i.e. the content of `CARGO_PKG_NAME` environmental variable.
    pub binary_name: &'static str,

    // VERGEN_BUILD_TIMESTAMP
    /// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.
    pub build_timestamp: &'static str,

    // VERGEN_BUILD_SEMVER
    /// Provides the build version, for example `0.1.0-9-g46f83e1`.
    pub build_version: &'static str,

    // VERGEN_GIT_SHA
    /// Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.
    pub commit_sha: &'static str,

    // VERGEN_GIT_COMMIT_TIMESTAMP
    /// Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.
    pub commit_timestamp: &'static str,

    // VERGEN_GIT_BRANCH
    /// Provides the name of the git branch that was used for the build, for example `master`.
    pub commit_branch: &'static str,

    // VERGEN_RUSTC_SEMVER
    /// Provides the rustc version that was used for the build, for example `1.52.0-nightly`.
    pub rustc_version: &'static str,

    // VERGEN_RUSTC_CHANNEL
    /// Provides the rustc channel that was used for the build, for example `nightly`.
    pub rustc_channel: &'static str,

    // VERGEN_CARGO_DEBUG
    /// Provides the cargo debug mode that was used for the build.
    // NOTE: keep the old name cargo_profile instead of cargo_debug for backwards compatibility
    pub cargo_profile: &'static str,

    // VERGEN_CARGO_TARGET_TRIPLE
    /// Provides the cargo target triple that was used for the build.
    pub cargo_triple: &'static str,
}

impl BinaryBuildInformation {
    // explicitly require the build_version to be passed as it's binary specific
    pub const fn new(binary_name: &'static str, build_version: &'static str) -> Self {
        let cargo_debug = env!("VERGEN_CARGO_DEBUG");
        let cargo_profile = if const_str::equal!(cargo_debug, "true") {
            "debug"
        } else {
            "release"
        };

        BinaryBuildInformation {
            binary_name,
            build_timestamp: env!("VERGEN_BUILD_TIMESTAMP"),
            build_version,
            commit_sha: env!("VERGEN_GIT_SHA"),
            commit_timestamp: env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
            commit_branch: env!("VERGEN_GIT_BRANCH"),
            rustc_version: env!("VERGEN_RUSTC_SEMVER"),
            rustc_channel: env!("VERGEN_RUSTC_CHANNEL"),
            cargo_profile,
            cargo_triple: env!("VERGEN_CARGO_TARGET_TRIPLE"),
        }
    }

    // Varient where we want to use the metadata generated by vergen in the consuming crate.
    pub const fn new_with_local_vergen(
        binary_name: &'static str,
        build_timestamp: &'static str,
        build_version: &'static str,
        commit_sha: &'static str,
        commit_timestamp: &'static str,
        commit_branch: &'static str,
    ) -> Self {
        let cargo_debug = env!("VERGEN_CARGO_DEBUG");
        let cargo_profile = if const_str::equal!(cargo_debug, "true") {
            "debug"
        } else {
            "release"
        };

        BinaryBuildInformation {
            binary_name,
            build_timestamp,
            build_version,
            commit_sha,
            commit_timestamp,
            commit_branch,
            rustc_version: env!("VERGEN_RUSTC_SEMVER"),
            rustc_channel: env!("VERGEN_RUSTC_CHANNEL"),
            cargo_profile,
            cargo_triple: env!("VERGEN_CARGO_TARGET_TRIPLE"),
        }
    }

    pub fn to_owned(&self) -> BinaryBuildInformationOwned {
        BinaryBuildInformationOwned {
            binary_name: self.binary_name.to_owned(),
            build_timestamp: self.build_timestamp.to_owned(),
            build_version: self.build_version.to_owned(),
            commit_sha: self.commit_sha.to_owned(),
            commit_timestamp: self.commit_timestamp.to_owned(),
            commit_branch: self.commit_branch.to_owned(),
            rustc_version: self.rustc_version.to_owned(),
            rustc_channel: self.rustc_channel.to_owned(),
            cargo_profile: self.cargo_profile.to_owned(),
            cargo_triple: self.cargo_triple.to_owned(),
        }
    }

    pub fn pretty_print(&self) -> String {
        self.to_owned().to_string()
    }
}

// to whoever is thinking of modifying this struct.
// you MUST NOT change its structure in any way - adding, removing or changing fields
// otherwise, it will break old clients as bincode serialisation is not backwards compatible
// even if you put `#[serde(default)]` all over the place
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "bin_info_schema", derive(schemars::JsonSchema))]
pub struct BinaryBuildInformationOwned {
    /// Provides the name of the binary, i.e. the content of `CARGO_PKG_NAME` environmental variable.
    pub binary_name: String,

    // VERGEN_BUILD_TIMESTAMP
    /// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.
    pub build_timestamp: String,

    // VERGEN_BUILD_SEMVER
    /// Provides the build version, for example `0.1.0-9-g46f83e1`.
    pub build_version: String,

    // VERGEN_GIT_SHA
    /// Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.
    pub commit_sha: String,

    // VERGEN_GIT_COMMIT_TIMESTAMP
    /// Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.
    pub commit_timestamp: String,

    // VERGEN_GIT_BRANCH
    /// Provides the name of the git branch that was used for the build, for example `master`.
    pub commit_branch: String,

    // VERGEN_RUSTC_SEMVER
    /// Provides the rustc version that was used for the build, for example `1.52.0-nightly`.
    pub rustc_version: String,

    // VERGEN_RUSTC_CHANNEL
    /// Provides the rustc channel that was used for the build, for example `nightly`.
    pub rustc_channel: String,

    // VERGEN_CARGO_DEBUG
    /// Provides the cargo debug mode that was used for the build.
    // NOTE: keep the old name cargo_profile instead of cargo_debug for backwards compatibility
    pub cargo_profile: String,

    // VERGEN_CARGO_TARGET_TRIPLE
    /// Provides the cargo target triple that was used for the build.
    #[serde(default = "unknown")]
    pub cargo_triple: String,
}

fn unknown() -> String {
    "unknown".to_string()
}

impl Display for BinaryBuildInformationOwned {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            r#"
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
            "Binary Name:",
            self.binary_name,
            "Build Timestamp:",
            self.build_timestamp,
            "Build Version:",
            self.build_version,
            "Commit SHA:",
            self.commit_sha,
            "Commit Date:",
            self.commit_timestamp,
            "Commit Branch:",
            self.commit_branch,
            "rustc Version:",
            self.rustc_version,
            "rustc Channel:",
            self.rustc_channel,
            "cargo Profile:",
            self.cargo_profile,
        )
    }
}

// since this macro will get expanded at the callsite, it will pull in correct binary version
#[macro_export]
macro_rules! bin_info {
    () => {
        $crate::build_information::BinaryBuildInformation::new(
            env!("CARGO_PKG_NAME"),
            env!("CARGO_PKG_VERSION"),
        )
    };
}

#[macro_export]
macro_rules! bin_info_owned {
    () => {
        $crate::build_information::BinaryBuildInformation::new(
            env!("CARGO_PKG_NAME"),
            env!("CARGO_PKG_VERSION"),
        )
        .to_owned()
    };
}

// variant that picks up the vergen build information generated by the build.rs in the consumer
// crate.
#[macro_export]
macro_rules! bin_info_local_vergen {
    () => {
        $crate::build_information::BinaryBuildInformation::new_with_local_vergen(
            env!("CARGO_PKG_NAME"),
            env!("VERGEN_BUILD_TIMESTAMP"),
            env!("CARGO_PKG_VERSION"),
            env!("VERGEN_GIT_SHA"),
            env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
            env!("VERGEN_GIT_BRANCH"),
        )
    };
}

#[macro_export]
macro_rules! bin_info_local_vergen_owned {
    () => {
        $crate::build_information::BinaryBuildInformation::new_with_local_vergen(
            env!("CARGO_PKG_NAME"),
            env!("VERGEN_BUILD_TIMESTAMP"),
            env!("CARGO_PKG_VERSION"),
            env!("VERGEN_GIT_SHA"),
            env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
            env!("VERGEN_GIT_BRANCH"),
        )
    };
}
