export STEP_RUNNER_VERSION ?= UNKNOWN

local := $(PWD)/.local
localBin := $(local)/bin

MODULE_NAME = gitlab.com/gitlab-org/step-runner

export GOBIN=$(localBin)
export PATH := $(localBin):$(PATH)

BIN_EXT=$(shell uname -s | sed 's/MINGW.*/.exe/; s/Linux//; s/Darwin//')

PROTOC := $(localBin)/protoc${BIN_EXT}
PROTOC_VERSION := 33.5

PROTOC_GEN_GO := protoc-gen-go
PROTOC_GEN_GO_VERSION := v1.36.11

PROTOC_GEN_GO_GRPC := protoc-gen-go-grpc
PROTOC_GEN_GO_GRPC_VERSION := v1.6.1

PROTOVALIDATE_VERSION := 1.1.1
PROTOVALIDATE_DIST := $(local)/protovalidate

PROTO_SRC := proto/step.proto
PROTO_GEN := $(wildcard proto/*.pb.go)

GOIMPORTS := goimports
GOIMPORTS_VERSION := v0.40.0

GOLANGCI_LINT := $(localBin)/golangci-lint
GOLANGCI_LINT_VERSION := v2.11.4

GOTESTSUM := gotestsum
GOTESTSUM_VERSION := v1.13.0

COVERAGE_OS ?= linux

GOCOVER_COBERTURA := gocover-cobertura
GOCOVER_COBERTURA_VERSION := v1.2.0

OCI_REGISTRY_SERVER := $(localBin)/oci-registry-server${BIN_EXT}

# override BUILD_OS_ARCH to build for multiple platforms
LOCAL_OS_ARCH := $(lastword $(shell go version))
BUILD_OS_ARCH ?= $(LOCAL_OS_ARCH)
PLATFORMS := $(foreach os_arch,$(BUILD_OS_ARCH),$(os_arch))
BIN_PATH := out/bin

.DEFAULT_GOAL := precommit
.PHONY: precommit
precommit: generate go-deps go-fmt go-lint test

.PHONY: $(PLATFORMS)
$(PLATFORMS): GOOS=$(firstword $(subst /, ,$@))
$(PLATFORMS): GOARCH=$(lastword $(subst /, ,$@))
$(PLATFORMS): BINARY=$(BIN_PATH)/step-runner-$(subst /,-,$@)$(if $(filter windows,$(GOOS)),.exe,)
$(PLATFORMS):
	@echo "Running build for step-runner"
	@echo "Building for GOOS=$(GOOS) GOARCH=$(GOARCH)"
	@echo "Output binary: $(BINARY)"
	@mkdir -p $(BIN_PATH)
	@CGO_ENABLED=0 GOOS="$(GOOS)" GOARCH="$(GOARCH)" go build \
		-ldflags '-X "$(MODULE_NAME)/cmd.stepRunnerVersion=$(STEP_RUNNER_VERSION)"' \
		-o "$(BINARY)"
	@echo "✅ Successfully built $(BINARY)"

# Build generates step-runner binaries for platforms listed in BUILD_OS_ARCH.
# If there is only one platform, the binary is copied to $BIN_PATH/step-runner for ease of use.
.PHONY: build
build: FIRST_PLATFORM=$(firstword $(BUILD_OS_ARCH))
build: PLATFORM_BINARY=$(BIN_PATH)/step-runner-$(subst /,-,$(FIRST_PLATFORM))$(if $(filter windows,$(firstword $(subst /, ,$(FIRST_PLATFORM)))),.exe,)
build: FIXED_LOCATION_BINARY=$(BIN_PATH)/step-runner
build: generate $(PLATFORMS)
	@echo "📋 Build completed for platforms: $(BUILD_OS_ARCH)"
	@echo "📂 Generated binaries:"
	@ls -la $(BIN_PATH)/step-runner-* 2>/dev/null || echo "No binaries found"
    ifeq (1, $(words $(BUILD_OS_ARCH)))
		@echo "📋 Single platform build detected, copying $(PLATFORM_BINARY) to $(FIXED_LOCATION_BINARY)"
		@cp $(PLATFORM_BINARY) $(FIXED_LOCATION_BINARY)
    endif

.PHONY: build-individual-commands
build-individual-commands:
	@echo "Running build for integration test step fixtures"
	@cd integration_test/steps/exit && go build -o run$(BIN_EXT)
	@cd integration_test/steps/sleep && go build -o run$(BIN_EXT)
	@cd integration_test/steps/copy_file && go build -o run$(BIN_EXT)
	@cd integration_test/steps/echo && go build -o run$(BIN_EXT)
	@cd integration_test/steps/secret_factory && go build -o run$(BIN_EXT)
	@cd integration_test/steps/export_env && go build -o run$(BIN_EXT)

$(PROTO_GEN): $(PROTO_SRC)
	$(MAKE) generate

.PHONY: .generate-proto
.generate-proto: $(PROTOC) $(PROTOC_GEN_GO) $(PROTOC_GEN_GO_GRPC) $(PROTOVALIDATE_DIST)
	@echo "Running generate proto"
	@go generate ./proto

.PHONY: generate
generate: .generate-proto go-fmt

$(PROTOC): OS_TYPE ?= $(shell uname -s | tr '[:upper:]' '[:lower:]' | sed 's/darwin/osx/' | sed 's/mingw.*/win64/')
$(PROTOC): ARCH ?= $(shell uname -m | sed 's/aarch64/aarch_64/' | sed 's/arm64/aarch_64/')
$(PROTOC): DOWNLOAD_URL = $(shell if [ "$(OS_TYPE)" = "win64" ]; then echo "https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-$(OS_TYPE).zip"; else echo "https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-$(OS_TYPE)-$(ARCH).zip"; fi)
$(PROTOC): OUT_DIR = $(shell dirname $(PROTOC))
$(PROTOC):
	# Installing $(DOWNLOAD_URL) as $(PROTOC)
	@mkdir -p "$(localBin)"
	@curl -sL "$(DOWNLOAD_URL)" -o "$(local)/protoc.zip"
	@unzip -u "$(local)/protoc.zip" -d "$(local)/"
	@chmod +x "$(PROTOC)"
	@rm "$(local)/protoc.zip"

.PHONY: $(PROTOC_GEN_GO)
$(PROTOC_GEN_GO):
	@go install google.golang.org/protobuf/cmd/protoc-gen-go@$(PROTOC_GEN_GO_VERSION)

.PHONY: $(PROTOC_GEN_GO_GRPC)
$(PROTOC_GEN_GO_GRPC):
	@go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@$(PROTOC_GEN_GO_GRPC_VERSION)

$(PROTOVALIDATE_DIST): DOWNLOAD_URL = https://github.com/bufbuild/protovalidate/archive/refs/tags/v$(PROTOVALIDATE_VERSION).zip
$(PROTOVALIDATE_DIST):
	# Downloading protovalidate import from $(DOWNLOAD_URL)
	@curl -sL "$(DOWNLOAD_URL)" -o "$(local)/protovalidate.zip"
	@unzip -q -u "$(local)/protovalidate.zip" -d "$(local)/"
	@rm -fr "$(local)/protovalidate"
	@mv -f "$(local)/protovalidate-$(PROTOVALIDATE_VERSION)" "$(local)/protovalidate"
	@rm "$(local)/protovalidate.zip"

.PHONY: clean
clean:
	@rm -rf $(BIN_PATH)
	@find . -name report.xml | xargs rm -f


.PHONY: check-generated
check-generated: generate
	@git --no-pager diff --compact-summary --exit-code && \
		git --no-pager diff --compact-summary --cached --exit-code

.PHONY: $(GOIMPORTS)
$(GOIMPORTS):
	@go install golang.org/x/tools/cmd/goimports@$(GOIMPORTS_VERSION)

.PHONY: $(GOTESTSUM)
$(GOTESTSUM):
	@go install gotest.tools/gotestsum@$(GOTESTSUM_VERSION)

.PHONY: $(GOCOVER_COBERTURA)
$(GOCOVER_COBERTURA):
	@go install github.com/boumenot/gocover-cobertura@$(GOCOVER_COBERTURA_VERSION)

.PHONY: test-dependencies
test-dependencies: $(OCI_REGISTRY_SERVER)

$(OCI_REGISTRY_SERVER):
	@go build -o $@ ./pkg/testutil/oci

# go-deps downloads Go dependencies if the go.sum file is not empty (avoids an error message)
.PHONY: go-deps
go-deps:
	@$(MAKE) \
	DESCRIPTION="$@" \
	COMMAND='if [ -s go.sum ]; then go mod download && go mod tidy; fi' \
	run-for-all-go-modules

# go-fmt formats Go files known to git (avoids running on .local/downloaded Go files)
# The Go module name is considered a local import
.PHONY: go-fmt
go-fmt: $(GOIMPORTS)
	@$(MAKE) \
	DESCRIPTION="$@" \
	COMMAND='git ls-files "**/*.go" | xargs $(GOIMPORTS) -w -local `awk '\''NR==1{print $$$$2; exit}'\'' go.mod`' \
	run-for-all-go-modules

$(GOLANGCI_LINT):
	@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(localBin) $(GOLANGCI_LINT_VERSION)

.PHONY: go-lint
go-lint: $(GOLANGCI_LINT)
	@$(MAKE) \
	DESCRIPTION="go-lint" \
	COMMAND='$(GOLANGCI_LINT) run --timeout 5m ./...' \
	run-for-all-go-modules

.PHONY: test
test: $(GOTESTSUM) generate test-dependencies build-individual-commands
	@mkdir -p out/coverage
	@$(MAKE) \
    DESCRIPTION="$@" \
    COMMAND='CGO_ENABLED=1 $(GOTESTSUM) \
        --junitfile=report.xml \
        --format=testname \
        --rerun-fails=2 \
        --rerun-fails-report=report.xml \
        --packages="./..." \
        -- -race -coverprofile=coverage.$(COVERAGE_OS).$$$$ONGOING_MODULE.out ./...' \
    run-for-all-go-modules
	@find . -path ./out/coverage -prune -o -name "coverage.*.out" -type f -print -exec mv {} out/coverage/ \;
	@echo "mode: set" > out/coverage/coverage.$(COVERAGE_OS).out
	@find out/coverage -name "coverage.$(COVERAGE_OS).*.out" -type f ! -name "coverage.$(COVERAGE_OS).out" -exec tail -q -n +2 {} + >> out/coverage/coverage.$(COVERAGE_OS).out

# runs a command for every directory that contains a go.mod
.PHONY: run-for-all-go-modules
run-for-all-go-modules: GO_MODS := $(wildcard ./go.mod */go.mod */*/go.mod */*/*/go.mod */*/*/*/go.mod */*/*/*/*/go.mod .gitlab/*/*/go.mod)
run-for-all-go-modules: GO_DIRS := $(dir $(GO_MODS))
run-for-all-go-modules:
	@for dir in $(GO_DIRS); do \
        echo "Running $(DESCRIPTION) for $$dir"; \
        export ONGOING_MODULE=$$(basename $$dir); \
        if [ "$$ONGOING_MODULE" = "." ]; then \
            export ONGOING_MODULE="step-runner"; \
        fi; \
        (cd "$$dir" && $(COMMAND)) || { \
            echo "ERROR: command failed."; \
            echo "       command: '$(COMMAND)'"; \
            exit 1; \
        } \
    done

.PHONY: cobertura_report
cobertura_report: $(GOCOVER_COBERTURA)
	@mkdir -p out/cobertura
	GOOS="$(COVERAGE_OS)" $(GOCOVER_COBERTURA) < out/coverage/coverage.$(COVERAGE_OS).out > out/cobertura/coverage.$(COVERAGE_OS).xml

GITLAB_RUNNER_BRANCH ?= main

.PHONY: test-runner-integration
test-runner-integration:
	@echo "--> Checking out gitlab-runner '$(GITLAB_RUNNER_BRANCH)' branch ..."
	@rm -rf /tmp/gitlab-runner-test
	@git clone --depth 1 --single-branch --branch $(GITLAB_RUNNER_BRANCH) https://gitlab.com/gitlab-org/gitlab-runner.git /tmp/gitlab-runner-test

	@echo "--> Checking local step-runner for dependency compatibility with gitlab runner ..."
	@cd /tmp/gitlab-runner-test &&\
	go mod edit -replace gitlab.com/gitlab-org/step-runner=$(CURDIR) &&\
	go mod tidy &&\
	go build -o gitlab-runner-test main.go &&\
	go build -o gitlab-runner-helper-test apps/gitlab-runner-helper/main.go &&\
	echo "No problems found."
