#!/usr/bin/env bash

# DESCRIPTION: Runs static analysis for Git-tracked Rust files reachable
# from the current directory:
# - Rust sources MUST end with a newline
# - Rust sources MUST include the Microsoft copyright header
# - Rust crate manifest files MUST contain:
#     [lints]
#     workspace = true
# - Rust sources MUST be formatted with `rustfmt`
# - The workspace MUST pass `cargo clippy -- --deny=warnings`
# - The workspace MUST have no unused direct dependencies (`cargo machete`)
#
# PARAMETERS: NONE

set -euo pipefail

CURDIR="$(pwd -P)"

# NOTE: Null-terminated strings are used for filenames; unset IFS.
IFS=

# Require ending newlines.
git ls-files -z -- '*.rs' \
| while read -r -d '' FILE; do
    if [ -n "$(tail -c 1 "${FILE}")" ]; then
        echo "ERROR: ${FILE} is missing a trailing newline" >&2
        exit 1
    fi
done

# Require copyright headers.
git ls-files -z -- '*.rs' \
| while read -r -d '' FILE; do
    # Skip files generated by the protocol compiler.
    if head -n 1 "${FILE}" | grep -q 'Code generated by Azure.Iot.Operations.ProtocolCompiler'; then
        continue
    fi

    HEADER_LINE_1="$(sed -n '1p' "${FILE}")"
    HEADER_LINE_2="$(sed -n '2p' "${FILE}")"

    if [ "${HEADER_LINE_1}" != "// Copyright (c) Microsoft Corporation." ] \
       || [ "${HEADER_LINE_2}" != "// Licensed under the MIT License." ]; then
        echo "ERROR: ${FILE} is missing the Microsoft copyright header" >&2
        exit 1
    fi
done

# Require `[lints] workspace = true` in every workspace manifest.
cargo metadata --format-version=1 --no-deps \
| jq -r '.packages[].manifest_path | select(startswith($cwd))' \
    --arg cwd "${CURDIR}" \
| while read -r FILE; do
    # Skip generated files.
    if head -n 1 "${FILE}" | grep -q 'Code generated by Azure.Iot.Operations.ProtocolCompiler'; then
        continue
    fi
    # Skip the connector scaffolding. It is a workspace member so it stays
    # compile-guarded against main, but it intentionally uses inline
    # `[lints.*]` instead of `[lints] workspace = true`: release tooling
    # turns it into a standalone downstream sample by stripping the `path`
    # keys, and `workspace = true` would not resolve outside the workspace.
    if [[ "${FILE}" == */sample_applications/sample_connector_scaffolding/Cargo.toml ]]; then
        continue
    fi
    if ! grep -q $'^\[lints\]\nworkspace = true$' "${FILE}"; then
        echo "ERROR: ${FILE} is missing [lints] workspace = true" >&2
        exit 1
    fi
done

# Require workspace dependency conventions: deps shared by >= 2 crates must be
# centralized in [workspace.dependencies], and centralized deps must be
# inherited (not re-declared inline). See rust/scripts/check_workspace_deps.py.
python3 "$(dirname "${BASH_SOURCE[0]}")/check_workspace_deps.py"

# Require `rustfmt` pass.
cargo fmt --verbose --all --check

# Require clippy pass over the full feature surface, including tests and
# examples. Validates developer-facing correctness.
cargo clippy --all --all-features --tests --examples -- --deny=warnings

# Require clippy pass for each feature configuration of the published
# library crates (no-default-features, each feature alone, all-features).
# Models what a downstream consumer sees when they depend on us with their
# own feature selection: `--ignore-private` filters to the libraries we
# ship; `--no-dev-deps` and `--lib` ensure the dependency graph matches
# a downstream resolve rather than our own dev/test setup.
cargo hack clippy \
    --workspace --ignore-private \
    --each-feature --no-dev-deps --lib \
    -- --deny=warnings

# Check for unused dependencies.
cargo machete
