APP=delphi_api
PACKAGE=$(subst _,-,$(APP))
STAGE?=dev

VENV_NAME ?= venv
VENV_ACTIVATE=. $(VENV_NAME)/bin/activate
PYTHON = $(VENV_NAME)/bin/python3

# Set AWS ECR address if your tools is going to be executed in a cloud
REGISTRY ?= $(APP)
REVISION ?= $(shell git rev-parse --short=8 HEAD)
REVISION_TEST ?= $(REVISION)-test
BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
# VERSION is overriden by CI/CD
VERSION ?= $(BRANCH)
APP_VERSION ?= $(shell cat ./VERSION)
APP_VERSION_TEST ?= $(APP_VERSION)-test

# Liquibase variables
DB_REGISTRY = delphi_api_db
DB_VERSION ?= $(shell cat ./liquibase/VERSION)
DB_VERSION_TEST ?= $(DB_VERSION)-test
BUILDTIME = $(shell date -u +"%Y%m%d%H%M%S")
VERSION_REVISION ?= ${DB_VERSION}-${REVISION}
TEST_ID = $(subst .,_,${DB_VERSION})
PG_CONTAINER_NAME = delphi-api-db-test__${TEST_ID}
NETWORK = ${PG_CONTAINER_NAME}-network__${TEST_ID}

# build sphinx docs
INSTALL_PACKAGE = $(PYTHON) setup.py install
AUTOGEN_DOC_STUBS = ENVIRONMENT=test sphinx-apidoc -f -o docs $(APP) */{proto,config,run}/*
BUILD_HTML_DOCS = cd docs && ENVIRONMENT=test make html && cd .. || cd ..
OPEN_HTML_DOCS = open ./docs/_build/html/readme.html

# protobuf configuration
PROTO_SOURCE_DIR = $(APP)/v3/proto
PROTO_OUTPUT_DIR = $(PROTO_SOURCE_DIR)/gen
PROTO_SOURCE_FILES = {amazon,apple,spotify,youtube}_v3.proto

# run freezeup to update requirements.txt
FREEZEUP = ./devutils dev.freezeup -y
FREEZEUP_DEV = ./devutils dev.freezeup -y --force --dev

# docker --ulimit value soft:hard
DOCKER_ULIMIT = nofile=32000:64000

# generate a migration file based on model changes
GENERATE_MIGRATION = ./bin/db-migrate.sh
RUN_MIGRATIONS = ./bin/db-upgrade.sh

##
## Commands:

##    docs | build the HTML docs using Sphinx
.PHONY:docs
docs: devenv
	$(VENV_ACTIVATE) && $(INSTALL_PACKAGE) \
		&& $(AUTOGEN_DOC_STUBS) && $(BUILD_HTML_DOCS) && $(OPEN_HTML_DOCS)

##    db-migrate | (DEPRECATED) generate a database migration script based on model changes
.PHONY:db-migrate
db-migrate: venv
	$(VENV_ACTIVATE) && $(GENERATE_MIGRATION)

##    db-upgrade | (DEPRECATED) runs the database migration scripts to bring the schema up-to-date
.PHONY:db-upgrade
db-upgrade: venv
	$(VENV_ACTIVATE) && $(RUN_MIGRATIONS)

##    codegen | compiles the protocol buffer files (.proto) to Python (requires protoc)
.PHONY:codegen
codegen:
	protoc -I=$(PROTO_SOURCE_DIR) --python_out=$(PROTO_OUTPUT_DIR) $(PROTO_SOURCE_FILES)

##    frozen | update the frozen requirements.txt file based on the requirements/base.pip file
.PHONY:frozen
frozen:
	$(FREEZEUP)

##    frozen-dev | update the frozen requirements-dev.txt file based on the requirements/dev.pip file
.PHONY:frozen-dev
frozen-dev:
	$(FREEZEUP_DEV)

##    sort-imports | run isort on the project and test modules
.PHONY:sort-imports
sort-imports:devenv
	$(VENV_ACTIVATE) && isort ./{$(APP),tests} --recursive

##    venv | create local virtual env from requirements.txt
venv: $(VENV_NAME)/bin/activate
$(VENV_NAME)/bin/activate: setup.py
	test -d $(VENV_NAME) || python3 -m venv $(VENV_NAME)
	cp pip.conf venv/
	$(PYTHON) -m pip install -U pip
	$(PYTHON) -m pip install -r requirements.txt
	$(PYTHON) -m pip install -e .
	touch $(VENV_NAME)/bin/activate

##    devenv | create local virtual env from requirements-dev.txt
.PHONY:devenv
devenv: venv
	$(PYTHON) -m pip install -r requirements-dev.txt

##    test | run pytest with coverage
.PHONY:test
test: devenv
	$(VENV_ACTIVATE) && pytest --cov=delphi_api --cov-report=term-missing

##    lint | run pylint on the codebase
.PHONY:lint
lint: devenv
	$(VENV_ACTIVATE) && pylint $(APP)

##    clean | clean unnecessary files
.PHONY: clean
clean:
	@rm -rf build/
	@rm -rf dist/
	@rm -rf *.egg-info
	@rm -rf *.pyc
	@rm -f $(PACKAGE)-*.tar.gz
	@rm -rf .*_cache
	@rm -rf htmlcov
	@rm -rf coverage.xml
	@rm -rf .coverage

##    docker/login | log docker into access repository. aws configuration needs to be set first
.PHONY: docker/login
docker/login:
	$$(aws ecr get-login --no-include-email --region us-east-1)

##    docker-compose/run | builds and runs the containers via docker-compose (recommended for dev).
.PHONY: docker-compose/run
docker-compose/run: docker/login
	touch ./envs/.env
	docker-compose build && \
		docker-compose up -d delphi-api-db && \
		docker exec delphi-api-db sh -c 'until pg_isready -U postgres; do sleep 1; done' && \
		docker-compose run delphi-api-db-liquibase && \
		docker-compose up -d delphi-api && \
		docker logs delphi-api -f --tail=200

##    docker/build | build api Docker image from Dockerfile
.PHONY: docker/build
docker/build: docker/login
	docker build \
		--ulimit $(DOCKER_ULIMIT) \
		--build-arg APP=$(APP) \
		--build-arg PACKAGE=$(PACKAGE) \
		--compress \
		-t $(REGISTRY):$(VERSION) \
		-t $(REGISTRY):$(REVISION) .

##    docker/run | run the API container. Note: use docker-compose instead for local dev.
.PHONY: docker/run
docker/run: docker/build
	docker run -v ~/.aws:/app/.aws:ro $(REGISTRY):$(REVISION)

##    docker/build-test | build the unit test docker container. run make docker/build first
.PHONY: docker/build-test
docker/build-test: docker/login docker/build-liquibase
	docker build \
		--ulimit $(DOCKER_ULIMIT) \
		--file Dockerfile.test \
		--build-arg REGISTRY=$(REGISTRY) \
		--build-arg REVISION=$(REVISION) \
		--build-arg APP=$(APP) \
		--build-arg PACKAGE=$(PACKAGE) \
		--compress \
		-t $(REGISTRY):$(REVISION_TEST) .

##    docker/push | (DEPRECATED) push the main docker container to ECR
.PHONY: docker/push
docker/push: docker/login
	docker push $(REGISTRY):$(VERSION)
	docker push $(REGISTRY):$(REVISION)

##    docker/build-liquibase | Build Liquibase docker image with DDL scripts
.PHONY: docker/build-liquibase
docker/build-liquibase: docker/login
	docker build \
		--build-arg REVISION=${REVISION} \
		--build-arg BUILDTIME=${BUILDTIME} \
		-t ${DB_REGISTRY}:${DB_VERSION} \
		-t ${DB_REGISTRY}:${BRANCH} \
		-t ${DB_REGISTRY}:${VERSION_REVISION} \
		-t ${DB_REGISTRY}:${REVISION_TEST} \
	    -f liquibase/Dockerfile ./liquibase

##    docker/clean | Remove docker test environment database container and network
.PHONY: docker/clean
docker/clean:
	@echo
	@echo "Cleaning up..."
	-docker kill ${PG_CONTAINER_NAME}
	-docker rm ${PG_CONTAINER_NAME}
	-docker network rm ${NETWORK}

##    docker/run-test | Run the full test suite in docker containers. Assumes docker/build has run.
.PHONY: docker/run-test
docker/run-test: docker/build-test
	$(MAKE) docker/clean
	docker run -d \
		-e POSTGRES_USER=postgres \
		-e POSTGRES_PASSWORD=postgres \
		-e POSTGRES_DB=test_db \
		-v $(shell pwd)/liquibase/tests/init_test_db_sql/:/docker-entrypoint-initdb.d \
		--name=${PG_CONTAINER_NAME} postgres:12

	docker exec ${PG_CONTAINER_NAME} sh -c 'until pg_isready -U postgres; do sleep 1; done'
	docker network create ${NETWORK}
	docker network connect ${NETWORK} ${PG_CONTAINER_NAME}

	docker run --rm \
		-e CREATE_USERS=1 \
		-e LIQUIBASE_URL=jdbc:postgresql://${PG_CONTAINER_NAME}:5432/test_db?createDatabaseIfNotExist=true \
		-e LIQUIBASE_USERNAME=postgres \
		-e LIQUIBASE_PASSWORD=postgres \
		-e LIQUIBASE_CHANGELOG=/liquibase/db.changelog-master.xml \
		--network ${NETWORK} \
		${DB_REGISTRY}:${REVISION_TEST} update

	# unit test container (also contains bigtable emulator)
	docker run --rm \
		-v ~/.aws:/app/.aws:ro \
		-e POSTGRES_ADDRESS=${PG_CONTAINER_NAME} \
		-e ENVIRONMENT=test \
		--network ${NETWORK} \
		$(REGISTRY):$(REVISION_TEST)
	$(MAKE) docker/clean

##    docker/build-run-test | Build and run the full test suite in docker containers.
.PHONY: docker/build-run-test
docker/build-run-test: docker/build docker/build-test docker/run-test

## |
## help | show this help info
.PHONY: help
help: Makefile
	@echo "Application: ${APP}\n"
	@echo "Run command:\n  make <target>\n"
	@grep -E -h '^## .*' $(MAKEFILE_LIST) | sed -n 's/^##//p'  | column -t -s '|' |  sed -e 's/^/ /'
