# LOCAL MACHINE
# This is a bunch of stuff that idk what even is this
uname 					:= $(shell uname)
mkfile_dir 				:= $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))

gdatecmd 				:= $(shell "command" -v gdate)
ifdef gdatecmd
gdate = gdate
else
gdate = date
endif
gdateok := $(shell 2>&1 $(gdate) --version | grep coreutils > /dev/null; echo $$?)
ifneq ($(gdateok), 0)
$(info **WARNING**: Missing coreutils "date" command. Please install coreutils.) # Needed for make dist
endif

gtarcmd 				:= $(shell "command" -v gtar)
ifdef gtarcmd
gtar = gtar
else
gtar = tar
endif
gtarok := $(shell 2>&1 $(gtar) --version | grep 'GNU tar' > /dev/null; echo $$?)
ifneq ($(gtarok), 0)
$(info **WARNING**: Missing GNU "tar" command. Please install gnu-tar.) # Needed for make dist
endif

permstr = -perm +111
ifneq ($(uname), Darwin)
permstr = -perm /111
endif

# These sections set variables for make to track timestamps for the purposes of rebuilding files
# PYTHON ENVIRONMENT
PYTHONHASHSEED 			= 10
VIRTUAL_ENV 			?= $(mkfile_dir)/.venv
root_dir 				= $(shell pwd)
python 					= $(VIRTUAL_ENV)/bin/python3
sphinx 					= $(python) -m sphinx
pip 					= $(python) -m pip
flake8 					= $(python) -m flake8
sphinx-apidoc 			= $(python) -m sphinx.ext.apidoc

# NSCLI
version 				= $(shell $(python) -c "import sys; sys.path.insert(0, \"$(mkfile_dir)\"); import nscli; print(nscli.__version__)")
requires.txt 			= nscli.egg-info/requires.txt
#egginfo 				= nscli.egg-info
config.db 				= nscli/data/config.db
nscli_files 			= $(shell find nscli -type f \! -name \*.pyc)
pkg_sources 			= $(nscli_files) $(manpages) $(completions)
pdm_list				= pdm_list
constraints.txt			= constraints.txt

# DOCUMENTATION
email_src 				= docs/src/ReleaseEmail.rst
email_html 				= docs/email/ReleaseEmail.html
changelog 				= docs/src/Changelog.rst
current_changelog 		= docs/src/CurrentChangelog.rst
news_files 				= $(shell find docs/src/newsfragments -type f)
regionlist 				= docs/src/RegionList.rst
docs/src 				= $(shell find docs/src -type f -name \*.rst)
webpage 				= docs/gh-pages
manpages 				= docs/man/nscli.1.gz docs/man/nscli_settings.5.gz
bash_completion 		= completions/bash/nscli
zsh_completion 			= completions/zsh/_nscli
completions 			= $(bash_completion) $(zsh_completion)

# PYTHON PACKAGING
tarball 				= dist/nscli-$(version).tar.gz
wheel 					= dist/nscli-$(version)-py3-none-any.whl

# MACOS PACKAGING
brew_formula 			= Formula/nscli.rb
brew_repository 		= $(shell brew --repository)
brew_arch				= $(shell brew unbottled nscli | grep "bottle status" | cut -d ':' -f2 | cut -d ' ' -f1)
bottle 					= dist/nscli-$(version).$(brew_arch).bottle.tar.gz

# DEBIAN PACKAGING
# The .deb installer files encode a lot of information in the filename. Here we declare the filenames we wish to create,
# which makes for easy targets. The recipe to make the .deb files parses relevant information from the desired filename
# (using the debparse_* macros defined below) and passes it to the build environment. To support a new .deb release,
# just declare the filename below and include it in $(debian_releases) for convenience. There needs to be a
# corresponding docker image available on the hub; for example, debian:10 or ubuntu:19.04.
debian10 				= dist/debian/nscli_$(version)-1debian1~10_amd64.deb
ubuntu2004 				= dist/debian/nscli_$(version)-1ubuntu1~20.04_amd64.deb
ubuntu2204 				= dist/debian/nscli_$(version)-1ubuntu1~22.04_amd64.deb
debian_releases 		= $(debian10) $(ubuntu2004) $(ubuntu2204)
debian_control_files 	= $(shell find debian -type f)
debparse_dist_id 		= $(shell echo $(1) | cut -d '-' -f 2 | cut -d '~' -f 1 | tr -d '[:digit:]')
debparse_dist_ver 		= $(shell echo $(1) | cut -d '-' -f 2 | cut -d '~' -f 2 | cut -d '_' -f 1)
debparse_image_name 	= nscli_builder_$(call debparse_dist_id,$1)_$(call debparse_dist_ver,$1):$(version)
debparse_codename 		= $(shell docker run --rm $(call debparse_image_name,$1) grep -m 1 CODENAME /etc/os-release | cut -d '=' -f 2)

.PHONY: all
all: help

.PHONY: clean
clean: docclean  ## Delete temporary files
	-rm -rf dist build .eggs nscli.egg-info .tox .coverage htmlcov coverage.xml .config share version
	-rm -f $(config.db)
	-find nscli -name __pycache__ -exec rm -rf "{}" \;
	-find nscli -name '*.pyc' -exec rm -f "{}" \;

.PHONY: distclean
distclean: clean  ## Delete anything that's not part of the repo
	git reset HEAD --hard
	git clean -fxd

.PHONY: test
test: $(config.db) $(manpages) $(python) $(completions) ## Run tests
	# Ensure necessary test credentials are loaded
	$(python) -m nscli.tests.tools
	# Run test suites
	$(python) -m tox

# .PHONY: dist
# dist: $(tarball) $(wheel)  ## Create a new distribution

.PHONY: release
#release: devenv lint test webpage_upload dist tag formula upload mirror_upload email ## Perform all the steps to release a new version of nscli
#release: release_env pdm_list build_env dist webpage debian brew_formula bottle

# make the tar.gz and whl
.PHONY: dist
dist: $(config.db) $(pkg_sources) $(manpages) $(python) | version_check
	$(python) -m build

.PHONY: bottle
bottle: $(bottle)  ## Create a new Homebrew bottle

.PHONY: debian
debian: $(debian_releases)  ## Create new Debian and Ubuntu packages

.PHONY: webpage
webpage: $(webpage)  ## Regenerate the webpage files

brew_formula: $(brew_formula)  ## Update the homebrew installer.

# upload the formula to homebrew-rs (gts/rs tap)
.PHONY: formula_upload
formula_upload: $(bottle)  ## Upload the homebrew installer to github.
	@echo "Making sure the OAUTHTOKEN environment variable is exported..."
	@test -n "$(OAUTHTOKEN)"
	@-rm -rf Formula/homebrew-rs
	@mkdir -p Formula
	@git clone -b nscli git@github.rackspace.com:SupportTools/homebrew-rs.git Formula/homebrew-rs
	@cp $(brew_formula) Formula/homebrew-rs/Formula
	@if ! [ -z "$$(git -C Formula/homebrew-rs status --porcelain -- Formula/nscli.rb)" ]; then \
		git -C Formula/homebrew-rs config --local user.email "$$(git config --local --get user.email)"; \
		git -C Formula/homebrew-rs config --local user.signingkey "$$(git config --local --get user.signingkey)"; \
		git -C Formula/homebrew-rs commit Formula/nscli.rb -m "update nscli.rb"; \
		git -C Formula/homebrew-rs push; \
		curl -fsS https://github.rackspace.com/api/v3/repos/SupportTools/homebrew-rs/pulls -H "Authorization: token $(OAUTHTOKEN)" -d '{"title":"update nscli.rb","body":"New version of NSCLI formula","head":"nscli","base":"master"}' > /dev/null || (echo "Make a PR manually at https://github.rackspace.com/SupportTools/homebrew-rs/compare/nscli" && /usr/bin/false);\
	else \
		echo "No change"; \
	fi

# something happened with editable installs and the location returned from importlib.metadata.distribution("nscli")
# we create a symlink to trick python that has nscli installed as an editable package where to find the files
.PHONY: site_link
site_link:
	ln -s $(root_dir)/nscli $(shell $(python) -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])') || echo "Link already exists"

.PHONY: devenv
devenv:  ## Set up your nscli dev environment. Creates a virtual environment.
	# ensure VENV is not active
	if [[ ! -z $$VIRTUAL_ENV ]]; then echo ; echo "!! ERROR !! Deactivate VENV before running 'make devenv'"; echo; exit 1; fi
	# install dev packages from lockfile and don't update lockfile
	-pdm venv remove in-project -y
	pdm sync -G dev
	make $(config.db)

.PHONY: build_env
build_env:  ## Set up your nscli dev environment. Creates a virtual environment.
	# ensure VENV is not active
	if [[ ! -z $$VIRTUAL_ENV ]]; then echo ; echo "!! ERROR !! Deactivate VENV before running 'make build_env'"; echo; exit 1; fi
	-pdm venv remove in-project -y
	# install build packages from lockfile and don't update lockfile
	pdm sync -G build
	make $(config.db)

.PHONY: release_env
release_env:  ## Set up your nscli dev environment. Creates a virtual environment.
	# ensure VENV is not active
	if [[ ! -z $$VIRTUAL_ENV ]]; then echo ; echo "!! ERROR !! Deactivate VENV before running 'make release_env'"; echo; exit 1; fi
	-pdm venv remove in-project -y
	# install prod packages from lockfile and don't update lockfile
	pdm sync --prod
	make $(config.db)

.PHONY: lint
lint: $(python) version_check  ## Check code style
	# Check for files with executable bits set. This should only be nscli/shared/2048.sh and nscli/__main__.py
	$(eval execs:=$(shell find nscli -type f $(permstr)))
	test "$(execs)" = "nscli/shared/2048.sh nscli/__main__.py" -o "nscli/__main__.py nscli/shared/2048.sh"
	# All directories and Python files in NSCLI must be lowercase and must not include spaces or dashes.
	! find nscli -type f -name '*.py' -o -type d | grep -e '[A-Z ]'
	# All Python packages under nscli must include a __init__.py file. If a directory contains Python code but no init,
	# the next line will error out.
	test -z `find nscli -type d '!' -exec test -e "{}/__init__.py" ';' -exec find "{}" -name '*.py' ';'`
	$(python) scripts/plugin_checker.py
	$(flake8) --config=$(mkfile_dir)/pyproject.toml

.PHONY: webpage_upload
webpage_upload: $(webpage) ## Upload the nscli documentation webpage to the gh-pages branch in GitHub
	# Verify the gh_pages branch exists. This branch should be pulled down, not created new
	git rev-parse --verify gh-pages || (echo "gh-pages branch not found. Try 'git fetch --all && git checkout gh-pages'" && exit 1)
	$(eval branch := $(shell git rev-parse --abbrev-ref HEAD))
	git checkout gh-pages
	git pull
	git rm -rf .  # un-stage all files
	git add docs/gh-pages/*  # stage only the output files we need
	git mv docs/gh-pages/* .  # move output files to root of repo
	touch .nojekyll && git add .nojekyll  # This file tells github to serve up the site as-is
	git commit --all -m "rebuild docs"
	git push
	git checkout $(branch)
	-$(python) -c 'import webbrowser; webbrowser.open("https://pages.github.rackspace.com/nsi/nscli/")'

.PHONY: email
email: $(email_html) $(python)  ## Send the release-day email
	$(python) scripts/send_release_email.py -t

.PHONY: upload
upload: $(tarball) $(wheel) $(python)  ## Upload a new distribution to artifactory.
	$(python) -m twine upload --repository-url https://artifacts.rackspace.net/artifactory/api/pypi/nscli-pypi-local $(tarball) $(wheel)

.PHONY: bottle_upload
bottle_upload: $(bottle)  ## Upload the built bottle to Artifactory
	@echo "Checking if a bottle by this name has already been uploaded..."
	@! curl -fsI "https://artifacts.rackspace.net/artifactory/nsi-bottles-local/$(bottle)" > /dev/null
	@echo "Making sure the API_TOKEN environment variable is exported..."
	@test -n "$(API_TOKEN)"
	@curl -i -H 'Authorization: Bearer $(API_TOKEN)' -T $(bottle) "https://artifacts.rackspace.net/artifactory/nsi-bottles-local/$(notdir $(bottle))"

.PHONY: version_check
version_check: $(python)
	@echo Validating version number...
	@$(python) scripts/versioning.py

.PHONY: docclean
docclean:
	# Delete cached documentation to avoid a lot of bugs in Sphinx. Especially required for sphinx_selective_exclude
	-find . -name .doctrees -exec rm -rf "{}" \;

.PHONY: banner
banner:
	@echo ""
	@echo "  ██╗    ██╗███████╗ ██████╗██╗     ██╗  "
	@echo "  ████╗  ██║██╔════╝██╔════╝██║     ██║  "
	@echo "  ██╔██╗ ██║███████╗██║     ██║     ██║  "
	@echo "  ██║╚██╗██║╚════██║██║     ██║     ██║  "
	@echo "  ██║ ╚████║███████║╚██████╗███████╗██║  "
	@echo "  ╚═╝  ╚═══╝╚══════╝ ╚═════╝╚══════╝╚═╝  "
	@echo ""

.PHONY: help
help: banner
# Help function shamelessly stolen from the Rackspace Engineering Handbook
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: buildcontainer
buildcontainer:
# Build the nscli testing environment locally. Best left to Jenkins.
	docker build --progress=plain --pull -t nscli_base:latest .
	test -f docker/credentials.json
	echo 'FROM nscli_base:latest' | docker build --progress=plain -t nscli_loaded:latest . -f -

# this creates a requirements.txt with "prod" packages from the .egg-info, the flag to use artifactory, and a forced install of importlib_metadata
requirements.txt: $(requires.txt)
	@printf "%s\n" '-i https://artifacts.rackspace.net/artifactory/api/pypi/pypi/simple/' > requirements.txt
	@cat $(requires.txt) | sed -e '/^$$/,$$d' >> requirements.txt
	# TODO: This next line is a gross hack
	@printf "%s\n" importlib_metadata >> requirements.txt

$(constraints.txt):
	$(python) scripts/generate_constraints.py > constraints.txt

$(pdm_list):
	# creating initial pdm_list for brew_formula use
	@pdm list | awk '!/nscli/ {if (NR>3) {print $$2}}' > pdm_list

.PHONY: pdm_lock
pdm_lock:
	# update lockfile
	pdm lock --strategy static_urls

version: nscli/__init__.py
	@printf "$(version)\n" > version && chmod 644 version

# creates a formula using scripts/make_formula.py and existing compiled data/hashes from the lockfile
$(brew_formula): $(python)
	# creating brew formula
	# This recipe can only run on a Mac
	@test "Darwin" = "$(uname)"
	mkdir -p "$(dir $(brew_formula))"
	$(python) scripts/make_formula.py scripts/formula_template.jinja2 $(mkfile_dir)/pdm_list $(brew_formula) $(version) $(shell shasum -a 256 $(tarball) | awk '{print $$1}')

# to support some changes with homebrew, we must build the bottle using the path of the nscli formula in the gts/rs tap
$(bottle): $(python) | $(brew_formula)
	# creating brew bottle
	# This recipe can only run on a Mac
	@test "Darwin" = "$(uname)"
#	HOMEBREW_MAKE_JOBS=64
	@echo "Checking if the Python sdist needs to be uploaded to artifactory..."
	@curl -fsI https://artifacts.rackspace.net/artifactory/api/pypi/pypi/packages/nscli/$(version)/nscli-$(version).tar.gz > /dev/null || make upload
	# Uninstalling nscli from brew
	@-brew uninstall --force nscli
	# brew install ./Formula/nscli.rb --build-bottle --formula
	@brew install ./Formula/nscli.rb --build-bottle --formula
	# mv $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb.bak
	@mv $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb.bak
	# cp ./Formula/nscli.rb $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/
	@cp ./Formula/nscli.rb $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/
	# brew bottle $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb --no-rebuild --json --root-url 'https://artifacts.rackspace.net/artifactory/nsi-bottles-local'
	@brew bottle $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb --no-rebuild --json --root-url 'https://artifacts.rackspace.net/artifactory/nsi-bottles-local'
	@#$(python) scripts/fixup_bottle.py nscli--$(version).$(brew_arch).bottle.json --root_url 'https://artifacts.rackspace.net/artifactory/nsi-bottles-local'
	# brew bottle ./nscli--$(version).$(brew_arch).bottle.json --merge --write --no-commit
	@brew bottle ./nscli--$(version).$(brew_arch).bottle.json --merge --write --no-commit
	# mv -f $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb ./Formula/nscli.rb
	@mv -f $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb ./Formula/nscli.rb
	# mv $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb.bak $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb
	@mv $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb.bak $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb
	# mv nscli--$(version).$(brew_arch).bottle.tar.gz $(bottle)
	@mv nscli--$(version).$(brew_arch).bottle.tar.gz $(bottle)
	# cat nscli--$(version).$(brew_arch).bottle.json | $(python) -m json.tool
	@cat nscli--$(version).$(brew_arch).bottle.json | $(python) -m json.tool
	# rm nscli--$(version).$(brew_arch).bottle.json
	@rm nscli--$(version).$(brew_arch).bottle.json
	# sleep 2 && touch $(bottle)
	@sleep 2 && touch $(bottle)
#	brew test-bot homebrew/core/nscli --keep-logs

%.deb: requirements.txt $(pkg_sources) $(manpages) $(debian_control_files)
	@mkdir -p "$(dir $@)"
	docker build --no-cache --platform linux/amd64 --progress=plain --pull -t $(call debparse_image_name,$@) \
		--build-arg VERSION=$(version) \
		--build-arg DIST_VERSION=$(call debparse_dist_ver,$@) \
		--build-arg DIST_ID=$(call debparse_dist_id,$@) \
		-f debian/Dockerfile .
	docker run --platform linux/amd64 --rm $(call debparse_image_name,$@) tar -C /dpkg -c "$(notdir $@)" | tar -C "$(dir $@)" -xv

.PHONY: debian_upload
debian_upload: $(debian_releases:.deb=.deb.upload)  ## Uploads the .deb release files to artifactory

%.deb.upload: %.deb
	$(eval FILE=$*.deb)
	@if test -z "$(API_TOKEN)"; then \
		echo "The API_TOKEN environment variable for artifactory is not exported."; \
		exit 1; \
	fi
	@if curl -fsI "https://artifacts.rackspace.net/artifactory/nscli-debian-local/pool/$(notdir $(FILE))" > /dev/null; then \
		echo $(notdir $(FILE)) already exists on artifactory.; \
		return 0; \
	fi
	@echo Uploading $(FILE)
	@curl -i -H "Authorization: Bearer $(API_TOKEN)" -XPUT -T $(FILE) \
	-H "X-Checksum-Sha1: $(shell shasum -a 1 $(FILE) | awk '{print $$1}')" -H "X-Checksum-Md5: $(shell md5sum $(FILE) | awk '{print $$1}')" \
	"https://artifacts.rackspace.net/artifactory/nscli-debian-local/pool/$(notdir $(FILE));deb.distribution=$(call debparse_codename,$(FILE));deb.component=main;deb.architecture=amd64" && echo

$(python):
	make build_env

$(webpage): $(regionlist) $(docs/src) $(python) $(constraints.txt)
	PYTHONHASHSEED=$(PYTHONHASHSEED) $(sphinx) -b html docs/src docs/gh-pages -D version="$(version)" -D release="$(version)"
	cp constraints.txt docs/gh-pages/

.PHONY: changelog
changelog: $(changelog)  ## Updates the changelog from the news files
$(changelog): $(news_files)
	$(python) -m towncrier --yes

$(current_changelog): $(changelog)
	head -n $$(($$(grep -n '=======' docs/src/Changelog.rst | head -n 2 | tail -n 1 | cut -d ':' -f 1)-2)) $(changelog) > $(current_changelog)

$(email_html): $(email_src) $(current_changelog) $(python) | docclean
	$(sphinx) -t email -b html docs/src docs/email -D version="$(version)" -D release="$(version)"

$(regionlist): $(config.db) $(python)
	$(python) scripts/print_regions.py > $(regionlist)

$(config.db): $(python) site_link
	$(python) -c 'from nscli.config_db import DataBase; DataBase("nscli/data/config.db")'

.PHONY: manpage
manpage: $(manpages)  ## Rebuild the man pages
$(manpages): docs/src $(regionlist) $(python) | docclean
	PYTHONHASHSEED=$(PYTHONHASHSEED) $(sphinx) -b man docs/src docs/man -D version="$(version)" -D release="$(version)"
	-rm -rf docs/man/*.gz
	gzip --no-name -9 docs/man/*

.PHONY: completions
completions: $(completions)  ## Rebuild the shell completion files

$(zsh_completion): $(nscli_files) $(python)
	@mkdir -p completions/zsh
	$(python) -m nscli settings --zsh > $(zsh_completion)

$(bash_completion): $(nscli_files) $(python)
	@mkdir -p completions/bash
	$(python) -m nscli settings --bash > $(bash_completion)

needs_review:  ## Generate a list of files that need review per NSCLI's SDLC policy.
	@-rm -f needs_review
	@$(eval all_files := $(shell git ls-tree --name-only -r HEAD nscli))
	@echo "Evaluating each file's commit history, please wait..."
	@for f in $(all_files); do \
		found=$$(git log --since='1 year ago' --pretty=format:"%ct %s" -- "$$f" | grep 'SDLC Review Passed'); \
		if [ -z "$$found" ]; then \
			git log --pretty=format:"%ct $$f%n" -n 1 -- "$$f" >> needs_review; \
		fi; \
	done
	@sort needs_review > needs_review_tmp
	@mv needs_review_tmp needs_review
