BUILD_NUMBER  ?= 1

PROJECT       ?= api
PROJECT_GROUP ?= marketing

BASE_AWS_PROFILE    = gdb-infra-dev
BASE_AWS_ACCOUNT_ID = 483193324480
BASE_AWS_REGION     = us-east-1
BASE_ECR_REGISTRY   = $(BASE_AWS_ACCOUNT_ID).dkr.ecr.$(BASE_AWS_REGION).amazonaws.com

AWS_PROFILE    ?= gdb-mct-dev
AWS_ACCOUNT_ID ?= 925412343888
AWS_REGION     ?= us-east-1
ENV_PREFIX     := dev
NAME_PREFIX    = $(ENV_PREFIX)-$(PROJECT_GROUP)
CLUSTER        = $(NAME_PREFIX)-clu
TASK_DEFINITION          = $(NAME_PREFIX)-api
ONE_TIME_TASK_DEFINITION = $(NAME_PREFIX)-api-one-time

ECR_REGISTRY        ?= $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com
ECR_REPOSITORY_NAME ?= $(PROJECT_GROUP)/$(PROJECT)
ECR_REGISTRY_URI    ?= $(ECR_REGISTRY)/$(ECR_REPOSITORY_NAME)

REVISION  ?= $(shell git rev-parse --short=8 HEAD)
BUILDTIME ?= $(shell date -u +"%Y%m%d%H%M%S")
VERSION   ?= build-$(BUILDTIME).$(REVISION)
SEMVER    ?= $(shell cat VERSION)

PYPI_INDEX_URL ?= https://pypi.org/simple

REDIS_IMAGE         ?= redis:3.2.10
POSTGRES_IMAGE      ?= postgres:12.10-alpine3.15
ELASTICSEARCH_IMAGE ?= docker.elastic.co/elasticsearch/elasticsearch:7.10.2

VENV := ./venv

TEST_IMAGE = ${ECR_REGISTRY}/${ECR_REPOSITORY_NAME}:${SEMVER}-testing

PYTHON = $(VENV)/bin/python
PIP    = $(VENV)/bin/pip
PYTEST = $(VENV)/bin/pytest
PYLINT = $(VENV)/bin/pylint
TWINE  = $(VENV)/bin/twine
YAPF   = $(VENV)/bin/yapf
ISORT  = $(VENV)/bin/isort
MYPY   = $(VENV)/bin/mypy
SAFETY = $(VENV)/bin/safety
FLAKE  = $(VENV)/bin/flake8
BANDIT = $(VENV)/bin/bandit
BLACK  = $(VENV)/bin/black

.DEFAULT_GOAL := help

## -
## help - this message
.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/^/ /'

## -
## Local commands:

$(VENV)/bin/activate:
	python -m venv $(VENV)/

##   venv - create local virtual env
venv: $(VENV)/bin/activate
	@echo "Creating virtual environment"
	poetry install

##   testenv - create local virtual env (tests.txt dependencies)
.PHONY:testenv
testenv: venv
	@echo "Installing requirements"
	poetry install

##   test - run tests locally
.PHONY:test
test: testenv
	@echo "Run tests"
	$(PYTEST) -vv --junitxml=test-reports/pytest-report.xml --cov=app --cov-config=.coveragerc --cov-report=xml --disable-pytest-warnings ./

##   checkstyle - run checkstyle locally
.PHONY:checkstyle
checkstyle: testenv
	@echo "Run checkstyle"
	$(ISORT) -rc -w 120 --check-only --quiet ./app/ || exit 0 # suppress errors TODO: fix errors
	$(FLAKE) ./app/ > test-reports/flake8-report.txt || exit 0 # suppress errors TODO: fix errors
	@cat test-reports/flake8-report.txt

##   dependencies-check - run safety locally
.PHONY:dependencies-check
dependencies-check: testenv
	@echo "Run dependencies check"
	$(SAFETY) check || exit 0 # suppress errors TODO: fix errors

##   dependencies-check - run bandit locally
.PHONY:owasp
owasp: testenv
	@echo "Run owasp check"
	$(BANDIT) -f json -o test-reports/bandit-report.json -r -x **/tests/* ./app || exit 0 # suppress errors TODO: fix errors

##   static-typing - run mypy locally
.PHONY:static-typing
static-typing: testenv
	@echo "Run static-typing check"
	$(MYPY) ./app/ || exit 0 # suppress errors TODO: fix errors

##   static-typing - run safety and bandit locally
.PHONY:security
security: dependencies-check owasp

##   prettify - run prettify locally
.PHONY:prettify
prettify: testenv
	$(BLACK) ./app/ --exclude='/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|venv|_build|buck-out|build|dist|migrations|.pytest_cache)/' --line-length=120 --target-version=py37
	# $(ISORT) -rc -w 120 ./app/ # import order is important

.PHONY:python/clean
python/clean:
	py3clean .

##   db-upgrade - run db migrations
.PHONY:db-upgrade
db-upgrade: testenv
	@echo "Run database migrations"
	cd ./app/ && $(PYTHON) manage.py db upgrade

.PHONY:pre-commit
pre-commit: docker/image/build docker/prettify docker/checkstyle docker/security docker/test

## -
## Version management

##   version/patch - increment version patch
.PHONY: version/patch
version/patch:
	@bumpversion \
		--config-file .bumpversion.cfg \
		--current-version ${SEMVER} \
		patch VERSION

##   version/minor - increment version minor
.PHONY: version/minor
version/minor:
	@bumpversion \
		--config-file .bumpversion.cfg \
		--current-version ${SEMVER} \
		minor VERSION

##   version/major - increment version major
.PHONY: version/major
version/major:
	@bumpversion \
		--config-file .bumpversion.cfg \
		--current-version ${SEMVER} \
		major VERSION

## -
## Docker commands:

##   docker/build-local - build using docker-compose for local testing
.PHONY:docker/build-local
docker/build-local: docker/login/base
	docker-compose build

##   docker/up-local - start dev environment
.PHONY:docker/up-local
docker/up-local: docker/login/base
	docker-compose up --build

##   docker/login/base - login to AWS ECR registry using docker, aws configuration need to be set first
.PHONY:docker/login/base
docker/login/base:
	@aws ecr get-login \
		--profile ${BASE_AWS_PROFILE} \
		--no-include-email \
		--region ${BASE_AWS_REGION} | awk '{print $$6}' | \
		docker login -u AWS --password-stdin https://$(BASE_ECR_REGISTRY) || aws ecr get-login-password \
			--profile ${BASE_AWS_PROFILE} \
			--region ${BASE_AWS_REGION} | \
			docker login --username AWS --password-stdin https://$(BASE_ECR_REGISTRY)

##   docker/login - login to AWS ECR registry using docker, aws configuration need to be set first
.PHONY:docker/login
docker/login:
	@aws ecr get-login \
		--profile ${AWS_PROFILE} \
		--no-include-email \
		--region ${AWS_REGION} | awk '{print $$6}' | \
		docker login -u AWS --password-stdin https://$(ECR_REGISTRY) || aws ecr get-login-password \
			--profile ${AWS_PROFILE} \
			--region ${AWS_REGION} | \
			docker login --username AWS --password-stdin https://$(ECR_REGISTRY)

##   docker/base/image/pull - pull base image from ECR
docker/base/image/pull: docker/login/base
	docker pull $(BASE_ECR_REGISTRY)/python:3.7-slim-runner
	docker pull $(BASE_ECR_REGISTRY)/python:3.7-slim-builder

##   docker/image/build - build lambda package for each environment and put all of them into one docker container
.PHONY: docker/image/build
docker/image/build: docker/login/base
	@echo "Building docker image"
	docker build \
		--build-arg VERSION=$(VERSION) \
		--build-arg REVISION=$(REVISION) \
		--build-arg BUILDTIME=$(BUILDTIME) \
		--build-arg PYPI_INDEX_URL=$(PYPI_INDEX_URL) \
		--target main \
		-t $(ECR_REGISTRY_URI):$(VERSION) \
		-t $(ECR_REGISTRY_URI):$(REVISION) .
	docker build \
		--build-arg VERSION=$(VERSION) \
		--build-arg REVISION=$(REVISION) \
		--build-arg BUILDTIME=$(BUILDTIME) \
		--build-arg PYPI_INDEX_URL=$(PYPI_INDEX_URL) \
		--target test \
		-t $(TEST_IMAGE) .

##   docker/image/push - push an image to a registry
.PHONY: docker/image/push
docker/image/push: docker/login
	@echo "Pushing docker image"
	docker push $(ECR_REGISTRY_URI):$(VERSION)
	docker push $(ECR_REGISTRY_URI):$(REVISION)

##   docker/test/dependncies - start dependencies
.PHONY: docker/test/dependencies
docker/test/dependencies:
	@echo "Create network if not exist"
	@docker network create test || exit 0
#	@echo "Start redis"
#	@docker run -d \
#		--mount type=tmpfs,tmpfs-size=128M,destination=/data \
#		--network test \
#		--name redis \
#		--rm \
#		$(REDIS_IMAGE)
#	@sleep 5
#	@docker logs --tail 10 redis
#	@echo "Start elasticsearch"
#	@docker run -d \
#		--mount type=tmpfs,tmpfs-size=128M,destination=/var/lib/elasticsearch/data \
#		--network test \
#		--name elasticsearch \
#		-e node.name=es01 \
#		-e cluster.name=docker-cluster \
#		-e network.host=0.0.0.0 \
#		-e discovery.zen.minimum_master_nodes=1 \
#		-e discovery.type=single-node \
#		-e xpack.security.enabled=false \
#		-e xpack.monitoring.enabled=false \
#		-e xpack.ml.enabled=false \
#		-e xpack.graph.enabled=false \
#		-e xpack.watcher.enabled=false \
#		-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
#		--rm \
#		$(ELASTICSEARCH_IMAGE)
#	@sleep 5
#	@docker logs --tail 10 elasticsearch
	@echo "Start db"
	@docker run -d \
		--mount type=tmpfs,destination=/var/lib/postgresql/data \
		--network test \
		--name postgres-test \
		-e POSTGRES_USER=postgres \
		-e POSTGRES_PASSWORD=postgres \
		-e POSTGRES_DB=postgres_test \
		--rm \
		$(POSTGRES_IMAGE)
	@sleep 5
	@docker logs --tail 10 postgres-test

.PHONY:docker/prepare-db-schema
docker/prepare-db-schemas: docker/test/dependencies
	docker exec postgres-test psql -U postgres -c "DROP DATABASE IF EXISTS postgres_test;"
	docker exec postgres-test psql -U postgres -c "CREATE DATABASE postgres_test;"
	docker exec -i postgres-test psql -U postgres postgres_test < ./app/migrations/structure.sql

.PHONY:docker/db-upgrade
docker/db-upgrade: docker/prepare-db-schemas
	@echo "Apply DB migrations"
	@docker run --rm \
		--network test \
		-e DEBUG=1 \
		-e DB_NAME=postgres_test \
		-e DB_USER=postgres \
		-e DB_PASS=postgres \
		-e DB_HOST=postgres-test \
		-e DB_PORT=5432 \
		-e ENV_CONFIG=test \
		-e TESTING=1 \
		-v $(PWD):/app \
		--workdir /app/app \
		$(TEST_IMAGE) sh -c "/venv/bin/python manage.py db upgrade"

##   docker/test - run tests with docker
.PHONY:docker/test/exec
docker/test/exec: docker/image/build
	@echo "Run tests"
	@docker run --rm \
		--network test \
		-e DEBUG=1 \
		-e DB_NAME=postgres_test \
		-e DB_USER=postgres \
		-e DB_PASS=postgres \
		-e DB_HOST=postgres-test \
		-e DB_PORT=5432 \
		-e ENV_CONFIG=test \
		-e TESTING=1 \
		-v $(PWD):/app \
		$(TEST_IMAGE) make VENV=/venv test

##   docker/test - run tests with docker
.PHONY:docker/test
docker/test: docker/image/build docker/clean docker/test/dependencies docker/db-upgrade docker/test/exec docker/clean

##   docker/clean - mocked command to support existing pipeline in jenkins
.PHONY:docker/clean
docker/clean:
	@echo "Nothing to clean inside docker, stopping containers if running"
	@docker stop postgres-test || exit 0
#	@docker stop redis || exit 0
#	@docker stop elasticsearch || exit 0
	@docker network rm test || exit 0
	@docker volume prune -f

##   docker/<local target> - universal docker wrapper for local targets
docker/%:
	docker run --rm \
		-v $(PWD):/app \
		$(TEST_IMAGE) make VENV=/venv $*

## :
## ECS commands

##   ecs/one-time/db-migrations : run database migrations task
.PHONY: ecs/one-time/db-migrations
ecs/one-time/db-migrations:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "db", "upgrade"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/index-recreate : run index recreation task
.PHONY: ecs/one-time/index-recreate
ecs/one-time/index-recreate:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "reindex_elastic"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	# aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/import-ccp-data : run import_ccp_data task
.PHONY: ecs/one-time/import-ccp-data
ecs/one-time/import-ccp-data:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "import_ccp_data"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/process-ccp-data : run process_ccp_data task
.PHONY: ecs/one-time/process-ccp-data
ecs/one-time/process-ccp-data:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "process_ccp_data"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/process-prs-data : run process_prs_data task
.PHONY: ecs/one-time/process-prs-data
ecs/one-time/process-prs-data:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "process_prs_data"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/import-prs-data : run import_prs_data task
.PHONY: ecs/one-time/import-prs-data
ecs/one-time/import-prs-data:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "import_prs_data"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/import-gras-data : run import_gras_data task
.PHONY: ecs/one-time/import-gras-data
ecs/one-time/import-gras-data:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "import_gras_data"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/import-campaigns-data : run import_campaigns_data task
.PHONY: ecs/one-time/import-campaigns-data
ecs/one-time/import-campaigns-data:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "import_campaigns_data"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/import-ad-sets-data : run import_ad_sets_data task
.PHONY: ecs/one-time/import-ad-sets-data
ecs/one-time/import-ad-sets-data:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "import_ad_sets_data"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/import-artists-moments : run import_artists_moments task
.PHONY: ecs/one-time/import-artists-moments
ecs/one-time/import-artists-moments:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "import_artists_moments"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/autoclaim-available-projects : run autoclaim_available_projects task
.PHONY: ecs/one-time/autoclaim-available-projects
ecs/one-time/autoclaim-available-projects:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "autoclaim_available_projects"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/import-stream-rates-data : run import_stream_rates_data task
.PHONY: ecs/one-time/import-stream-rates-data
ecs/one-time/import-stream-rates-data:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "import_stream_rates_data"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/import-projects-data : run import_projects_data task
.PHONY: ecs/one-time/import-projects-data
ecs/one-time/import-projects-data:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "import_projects_data"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/register-fivetran-webhook : run register_fivetran_webhook task
.PHONY: ecs/one-time/register-fivetran-webhook
ecs/one-time/register-fivetran-webhook:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "register_fivetran_webhook"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}

##   ecs/one-time/copy-labels-data : run copy_labels_data task
.PHONY: ecs/one-time/copy-labels-data
ecs/one-time/copy-labels-data:
	$(eval TASK_ARN=$(shell jq -r .${ENV_PREFIX} infra/ecs/network-configuration.json | xargs -0 aws ecs run-task --profile ${AWS_PROFILE} --task-definition=${ONE_TIME_TASK_DEFINITION} --overrides '{"containerOverrides": [{"name": "api", "command": ["python", "manage.py", "copy_labels_data"]}]}' --cli-input-json | jq -r '.tasks[].taskArn'))
	aws ecs wait tasks-stopped --cluster ${CLUSTER} --tasks ${TASK_ARN} --profile ${AWS_PROFILE}
