# Misc
name ?= exec_sandbox
python_version ?= 3.12  # Lowest compatible version (see pyproject.toml requires-python)

# System version pins + content hashes — managed by `make upgrade`
# (scripts/upgrade-versions.sh). versions.lock is the single source of truth:
# build scripts and sub-makes read it themselves; nothing passes versions around.
# qemu pin exists for CI build-from-source (Ubuntu 24.04 ships 8.2.2 with ARM64 TCG bugs).
include versions.lock

# Versions
version_full ?= $(shell $(MAKE) --silent version-full)
version_small ?= $(shell $(MAKE) --silent version)

# Flamegraph
PROFILES_DIR := profiles
PYSPY_RATE := 1000
PYSPY_BASETEMP := /tmp/pytest-flamegraph
PYSPY_OUTPUT = $(PROFILES_DIR)/flamegraph_$(shell date +%Y%m%d_%H%M%S).json

version:
	@bash ./cicd/version.sh -g . -c

version-full:
	@bash ./cicd/version.sh -g . -c -m

version-pypi:
	@bash ./cicd/version.sh -g .

rust-version:
	@echo $(RUST_VERSION)

alpine-version:
	@echo $(ALPINE_VERSION)

qemu-version:
	@echo $(QEMU_VERSION)

# ============================================================================
# Installation
# ============================================================================

install-sys:
	brew install qemu shellcheck

install:
	uv venv --python $(python_version) --allow-existing
	$(MAKE) install-deps
	$(MAKE) --directory guest-agent install
	$(MAKE) --directory tiny-init install
	$(MAKE) --directory gvproxy-wrapper install
	$(MAKE) install-seek

# Code search using seek (trigram index via zoekt, sub-second queries)
# Usage: seek "pattern" (on PATH via direnv)
# See: https://github.com/dualeai/seek
install-seek:
	@curl -sSfL https://raw.githubusercontent.com/dualeai/seek/main/install.sh | sh

install-deps:
	uv sync --extra dev --extra s3

# Build QEMU from source (Linux CI only — Ubuntu 24.04 ships 8.2.2 with ARM64 TCG bugs)
# Usage: make build-qemu [QEMU_PREFIX=~/qemu-build]
build-qemu:
	./scripts/build-qemu.sh

# ============================================================================
# Upgrade (auto-called targets for dependency updates)
# ============================================================================

# Refresh versions.lock first — each sub-make below re-parses the lock on
# invocation, so the cargo upgrades run in the freshly pinned rust image.
upgrade:
	./scripts/upgrade-versions.sh
	uv lock --upgrade --refresh
	$(MAKE) build-catalogs
	$(MAKE) --directory guest-agent upgrade
	$(MAKE) --directory tiny-init upgrade
	$(MAKE) --directory gvproxy-wrapper upgrade

# Build package allow-lists from PyPI and npm registries
build-catalogs:
	@echo "📦 Building package catalogs (PyPI + npm top 10k)..."
	uv run --script scripts/build_package_catalogs.py src/exec_sandbox/resources

# ============================================================================
# Building
# ============================================================================

IMAGE_ARCH ?= $$(uname -m)
IMAGE_VARIANT ?= all

# Build everything: VM images (guest-agent, tiny-init, kernel, qcow2) + host binaries (gvproxy-wrapper).
# Native arch by default, use IMAGE_ARCH=all for cross-arch.
# Usage: make build [IMAGE_ARCH=all|x86_64|aarch64] [IMAGE_VARIANT=python|node|raw|all]
# Note: arm64 is normalized to aarch64 in the recipe to match script expectations.
build:
	$(MAKE) --directory gvproxy-wrapper build
	@echo "🔨 Building QEMU images (arch=$(IMAGE_ARCH), variant=$(IMAGE_VARIANT))..."
	./scripts/build-images.sh $$(echo "$(IMAGE_ARCH)" | sed 's/arm64/aarch64/') $(IMAGE_VARIANT)

# Kept as alias for backwards compatibility and CI scripts.
build-images: build

# ============================================================================
# Testing
# ============================================================================

test:
	$(MAKE) test-static
	$(MAKE) test-func

test-static:
	uv run ruff format --check .
	uv run ruff check .
	uv run pyright .
	uv run -m vulture src/ scripts/ --min-confidence 80
	shellcheck scripts/*.sh cicd/*.sh
	$(MAKE) test-kernel-hash-sync
	$(MAKE) --directory guest-agent test-static
	$(MAKE) --directory tiny-init test-static
	$(MAKE) --directory gvproxy-wrapper test-static

# Kernel hash functions must stay byte-identical between the two scripts:
# extract-kernel.sh writes the .hash sidecar that build-kernel.sh reads —
# divergence makes extract-kernel serve a stale vmlinux/initramfs silently.
test-kernel-hash-sync:
	@a=$$(mktemp) && b=$$(mktemp) && \
		sed -n '/^compute_hash() {/,/^}/p' scripts/build-kernel.sh | tail -n +2 > "$$a" && \
		sed -n '/^compute_kernel_hash() {/,/^}/p' scripts/extract-kernel.sh | tail -n +2 > "$$b" && \
		{ diff "$$a" "$$b" || { echo "ERROR: compute_hash (build-kernel.sh) and compute_kernel_hash (extract-kernel.sh) diverged"; rm -f "$$a" "$$b"; exit 1; }; } && \
		rm -f "$$a" "$$b"

# CI resource monitor — wraps a command with background metrics collection.
# Runs in a single shell: start monitor, run command, kill monitor, propagate exit code.
# Usage: $(call ci-monitor-run,<command>)
define ci-monitor-run
@./scripts/ci-resource-monitor.sh & _monitor_pid=$$!; \
	$(1); _rc=$$?; \
	kill $$_monitor_pid 2>/dev/null || true; \
	wait $$_monitor_pid 2>/dev/null || true; \
	exit $$_rc
endef

# All tests together for accurate coverage measurement (excludes sudo and slow tests)
test-func:
	$(call ci-monitor-run,uv run pytest tests/ -v -n auto -m "not sudo and not slow" --ignore=tests/benchmarks)

# Tests requiring sudo privileges (parallelised — each test uses isolated tmp_path / UUID cgroups)
test-sudo:
	$(call ci-monitor-run,uv run pytest tests/ -v -n auto -m "sudo" --ignore=tests/benchmarks)

# Host functional suite plus Rust/Go unit tests (excludes sudo and slow)
test-unit:
	$(MAKE) test-func
	$(MAKE) --directory guest-agent test-unit
	$(MAKE) --directory tiny-init test-unit
	$(MAKE) --directory gvproxy-wrapper test-unit

# Memory leak detection tests (slow, run sequentially for accurate measurement)
test-slow:
	$(call ci-monitor-run,uv run pytest tests/ -v -n 0 -m slow --ignore=tests/benchmarks)

# ============================================================================
# Linting
# ============================================================================

lint:
	uv run ruff format .
	uv run ruff check --fix .
	$(MAKE) --directory guest-agent lint
	$(MAKE) --directory tiny-init lint
	$(MAKE) --directory gvproxy-wrapper lint

# ============================================================================
# CI Monitoring (requires: gh cli)
# Usage: make ci-status [run_id=ID] or make ci-diagnose [run_id=ID]
# ============================================================================

run_id ?=

ci-status:
	@uv run --script scripts/ci_diagnose.py status $(run_id)

ci-diagnose:
	@uv run --script scripts/ci_diagnose.py diagnose $(run_id)

# ============================================================================
# Benchmarking (concurrent VM latency)
# ============================================================================

# Usage: make bench-pool [BENCH_POOL=N]  (N warm VMs per language)
BENCH_POOL ?= 5

bench:
	uv run python scripts/benchmark_latency.py -n 10

bench-pool:
	uv run python scripts/benchmark_latency.py -n 10 --pool $(BENCH_POOL)

# 2D overcommit optimizer — finds Pareto-optimal (CPU_OC, MEM_OC) configs.
# Fires N VMs per combo across a 4x4 grid, ranks by Sharpe-like efficiency
# (throughput / RSS fraction), and reports the efficient frontier.
# Run on a KVM-capable host for production-representative results.
# Usage: make bench-optimizer [N_VMS=200]
bench-optimizer:
	uv run --script scripts/benchmark_optimizer.py $(if $(N_VMS),-n $(N_VMS),)

# VM density benchmark — measures host memory cost per VM.
# Boots N VMs, holds them alive, samples host memory to compute marginal cost.
# Usage: make bench-density [N_VMS=10]
#        make bench-density-sweep          # sweep N=5,10,20 VMs
bench-density:
	uv run --script scripts/benchmark_density.py $(if $(N_VMS),-n $(N_VMS),) --workload

bench-density-sweep:
	uv run --script scripts/benchmark_density.py --sweep 3 5 10 --workload --json

# ============================================================================
# Flamegraph Profiling (requires sudo on macOS)
# ============================================================================

test-flamegraph:
	@mkdir -p $(PROFILES_DIR)
	@echo "Profiling tests (requires sudo for py-spy)..."
	uv run py-spy record \
		--subprocesses \
		--rate $(PYSPY_RATE) \
		--format speedscope \
		--output $(PYSPY_OUTPUT) \
		-- uv run pytest tests/ -v -n auto --no-cov --basetemp=$(PYSPY_BASETEMP) -m "not sudo" --ignore=tests/benchmarks
	@echo "Flamegraph saved to $(PYSPY_OUTPUT)"
	@echo "Open at https://speedscope.app for interactive filtering (search 'exec_sandbox')"

test-flamegraph-pattern:
	@mkdir -p $(PROFILES_DIR)
	@echo "Profiling: $(PATTERN)"
	uv run py-spy record \
		--subprocesses \
		--rate $(PYSPY_RATE) \
		--format speedscope \
		--output $(PYSPY_OUTPUT) \
		-- uv run pytest tests/$(PATTERN) -v -n 0 --no-cov --basetemp=$(PYSPY_BASETEMP)
	@echo "Flamegraph saved to $(PYSPY_OUTPUT)"
	@echo "Open at https://speedscope.app for interactive filtering (search 'exec_sandbox')"

bench-flamegraph:
	@mkdir -p $(PROFILES_DIR)
	@echo "Profiling benchmark (requires sudo for py-spy)..."
	uv run py-spy record \
		--subprocesses \
		--rate $(PYSPY_RATE) \
		--format speedscope \
		--output $(PYSPY_OUTPUT) \
		-- uv run python scripts/benchmark_latency.py -n 10
	@echo "Flamegraph saved to $(PYSPY_OUTPUT)"
	@echo "Open at https://speedscope.app for interactive filtering (search 'exec_sandbox')"

bench-pool-flamegraph:
	@mkdir -p $(PROFILES_DIR)
	@echo "Profiling benchmark with pool (requires sudo for py-spy)..."
	uv run py-spy record \
		--subprocesses \
		--rate $(PYSPY_RATE) \
		--format speedscope \
		--output $(PYSPY_OUTPUT) \
		-- uv run python scripts/benchmark_latency.py -n 10 --pool $(BENCH_POOL)
	@echo "Flamegraph saved to $(PYSPY_OUTPUT)"
	@echo "Open at https://speedscope.app for interactive filtering (search 'exec_sandbox')"

# ============================================================================
# Cleanup
# ============================================================================

clean:
	$(MAKE) --directory guest-agent clean
	$(MAKE) --directory tiny-init clean
	$(MAKE) --directory gvproxy-wrapper clean
	rm -rf .pytest_cache .coverage htmlcov
