//! Build script that stamps `mcptest_core::version` with build-time facts.
//!
//! Why this exists: CI needs to bake the commit sha and the workflow
//! timestamp into the binary so a deployed `mcptest --version` tells the
//! operator which build they are running, not just what the Cargo.toml
//! says. We read three opt-in env vars from CI:
//!
//! * `MCPTEST_BUILD_SHA`: long commit sha (truncated to 12 chars).
//! * `MCPTEST_BUILD_TIMESTAMP`: RFC 3339 of the workflow run.
//! * `MCPTEST_BUILD_DIRTY`: `0` (clean) or `1` (working tree had unstaged
//!   changes; CI always sets `0`).
//!
//! Local dev fallback: when those env vars are not set we shell out to
//! `git` to discover the sha and dirty bit so a developer who runs
//! `cargo build` in a checkout still gets a useful version string. If
//! `git` is missing or the directory is not a git repo the build script
//! writes empty strings; the accessors return `None`.
//!
//! All output goes through `cargo:rustc-env=...` so the `version` module
//! can pick it up with `env!`/`option_env!` at compile time. No runtime
//! cost, no dependency on the build script at runtime.

use std::process::Command;

fn main() {
    // Rebuild whenever the CI env vars change, but do not over-eagerly
    // rerun on every source change. Cargo already handles source rebuilds.
    println!("cargo:rerun-if-env-changed=MCPTEST_BUILD_SHA");
    println!("cargo:rerun-if-env-changed=MCPTEST_BUILD_TIMESTAMP");
    println!("cargo:rerun-if-env-changed=MCPTEST_BUILD_DIRTY");
    println!("cargo:rerun-if-changed=build.rs");

    let env_sha = std::env::var("MCPTEST_BUILD_SHA").ok();
    let env_ts = std::env::var("MCPTEST_BUILD_TIMESTAMP").ok();
    let env_dirty = std::env::var("MCPTEST_BUILD_DIRTY").ok();

    // Sha: prefer env (CI), fall back to git. Truncate to 12 chars so the
    // string we print stays human-readable.
    let sha = env_sha
        .filter(|s| !s.is_empty())
        .or_else(git_sha)
        .map(|s| truncate(&s, 12))
        .unwrap_or_default();

    let timestamp = env_ts.filter(|s| !s.is_empty()).unwrap_or_default();

    let dirty = match env_dirty {
        Some(v) if v == "1" || v.eq_ignore_ascii_case("true") => true,
        Some(_) => false,
        None => git_dirty(),
    };

    let pkg_version = std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| String::new());
    let full = build_full(&pkg_version, &sha, dirty);

    println!("cargo:rustc-env=MCPTEST_BUILD_SHA_RESOLVED={sha}");
    println!("cargo:rustc-env=MCPTEST_BUILD_TIMESTAMP_RESOLVED={timestamp}");
    println!(
        "cargo:rustc-env=MCPTEST_BUILD_DIRTY_RESOLVED={}",
        if dirty { "1" } else { "0" }
    );
    println!("cargo:rustc-env=MCPTEST_VERSION_FULL={full}");
    println!(
        "cargo:rustc-env=MCPTEST_BUILD_TARGET={}",
        std::env::var("TARGET").unwrap_or_default()
    );
}

fn build_full(pkg_version: &str, sha: &str, dirty: bool) -> String {
    if sha.is_empty() && !dirty {
        return pkg_version.to_string();
    }
    let mut out = String::with_capacity(pkg_version.len() + 24);
    out.push_str(pkg_version);
    out.push('+');
    if !sha.is_empty() {
        out.push_str(sha);
    }
    if dirty {
        if !sha.is_empty() {
            out.push('.');
        }
        out.push_str("dirty");
    }
    out
}

fn truncate(s: &str, n: usize) -> String {
    if s.len() <= n {
        s.to_string()
    } else {
        s[..n].to_string()
    }
}

fn git_sha() -> Option<String> {
    let out = Command::new("git")
        .args(["rev-parse", "HEAD"])
        .output()
        .ok()?;
    if !out.status.success() {
        return None;
    }
    let s = String::from_utf8(out.stdout).ok()?.trim().to_string();
    if s.is_empty() {
        None
    } else {
        Some(s)
    }
}

fn git_dirty() -> bool {
    let out = match Command::new("git").args(["status", "--porcelain"]).output() {
        Ok(o) => o,
        Err(_) => return false,
    };
    if !out.status.success() {
        return false;
    }
    !out.stdout.is_empty()
}
