# ==========================
#  Universal Makefile (namespaced only)
#  Author: Sergey Antropov
#  https://devops.org.ru
# ==========================

# Замена пробелов на табы в консоли (фикс ошибок)
# sed -i 's/^    /\t/' makefile

# ---- Предустановленные значения под вашу сборку ----
REGISTRY      ?= hub.cism-ms.ru/library
IMAGE         ?=
TAG           ?= latest
CONTEXT       ?= .
DOCKERFILE    ?= Dockerfile

# ---- Режимы сборки ----
USE_BUILDX    ?= 0                 # 1 -> использовать docker buildx
PLATFORMS     ?= linux/amd64       # например: linux/amd64,linux/arm64
PUSH          ?= 0                 # 1 -> пушить образ в buildx (в рамках build)
LOAD          ?= 1                 # 1 -> загрузить образ в локальный докер (buildx)
NO_CACHE      ?= 0                 # 1 -> без кэша
PULL          ?= 0                 # 1 -> принудительно тянуть base-образы
QUIET         ?= 0                 # 1 -> тихий билд (минимум вывода)

# ---- Build args ----
BUILD_ARGS    ?=                   # строка с доп. флагами: --build-arg FOO=bar --build-arg BAZ=qux
ARG_FILE      ?=                   # путь к файлу KEY=VAL; будет превращён в --build-arg KEY=VAL

# ---- Проверка контейнера ----
RUN_CMD ?= lsb_release -a          # Что проверить после сборки (docker check)

# ---- Buildx ----
BUILDER       ?= universal-builder

# ---- Логи ----
LOG_DIR       ?= logs
LOG_FILE      ?= $(LOG_DIR)/build_$(shell date +%Y%m%d_%H%M%S).log

# --- Настройки Git ---
GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
GIT_REMOTE ?= origin
GIT_MSG    ?=

# ======================
#  OFFLINE LOAD/RETAG/PUSH
# ======================
ARCHIVE       ?= image.tar           # путь к docker save архиву
LOGIN         ?= 1                   # 1 -> docker login в REGISTRY перед push
PUSH_OFFLINE  ?= 1                   # 1 -> docker push после load
KEEP_SRC_TAG  ?= 1                   # 1 -> если IMAGE пуст, взять тег из архива

OFFLINE_DIR   ?= .offline
LOAD_LOG      := $(OFFLINE_DIR)/load.log
SRC_FILE      := $(OFFLINE_DIR)/src_image.txt
DST_FILE      := $(OFFLINE_DIR)/dst_image.txt
LAST_SAVE     := $(OFFLINE_DIR)/last_save.txt

# ======================
#  Universal save (local)
# ======================
IMAGES_DIR    ?= images             # куда сохраняем архивы
SRC_IMAGE     ?=                    # образ-источник; если пусто — $(FULL_IMAGE)
COMPRESS      ?= 0                  # 1 -> gzip архив
SPLIT_SIZE    ?=                    # размер чанка, напр.: 2G (пусто = не резать)

# ---- Полное имя образа ----
ifneq ($(strip $(REGISTRY)),)
  FULL_IMAGE := $(REGISTRY)/$(IMAGE):$(TAG)
else
  FULL_IMAGE := $(IMAGE):$(TAG)
endif

# ---- Цвета ----
COLOR_RESET := \033[0m
COLOR_INFO  := \033[1;34m
COLOR_OK    := \033[1;32m
COLOR_ERR   := \033[1;31m
COLOR_WARN  := \033[1;33m

# ---- Флаги сборки ----
ifeq ($(NO_CACHE),1)
  CACHE_FLAG := --no-cache
else
  CACHE_FLAG :=
endif

ifeq ($(PULL),1)
  PULL_FLAG := --pull
else
  PULL_FLAG :=
endif

ifeq ($(QUIET),1)
  QUIET_FLAG := --quiet
else
  QUIET_FLAG :=
endif

# Аргументы из файла KEY=VALUE -> --build-arg KEY=VALUE
ifneq ($(strip $(ARG_FILE)),)
  ARG_FILE_FLAGS := $(shell sed -n 's/^\([A-Za-z_][A-Za-z0-9_]*\)=\(.*\)/--build-arg \1=\2/p' $(ARG_FILE))
else
  ARG_FILE_FLAGS :=
endif

# ======================
#         Help
# ======================
.PHONY: help
help:
	@echo "Namespaces:"
	@echo "  make docker [build|check|push|save|load|load-raw|inspect|retag|create-builder|use-builder|login|clean|print-config]"
	@echo "  make git    [status|pull|commit|push|sync]"
	@echo ""
	@echo "Key vars (override via make VAR=value):"
	@echo "  REGISTRY=$(REGISTRY)  IMAGE=$(IMAGE)  TAG=$(TAG)  CONTEXT=$(CONTEXT)  DOCKERFILE=$(DOCKERFILE)"
	@echo "  USE_BUILDX=$(USE_BUILDX)  PLATFORMS=$(PLATFORMS)  PUSH=$(PUSH)  LOAD=$(LOAD)  NO_CACHE=$(NO_CACHE)  PULL=$(PULL)  QUIET=$(QUIET)"
	@echo "  BUILD_ARGS=$(BUILD_ARGS)  ARG_FILE=$(ARG_FILE)  RUN_CMD=$(RUN_CMD)"
	@echo "  ARCHIVE=$(ARCHIVE)  LOGIN=$(LOGIN)  PUSH_OFFLINE=$(PUSH_OFFLINE)  KEEP_SRC_TAG=$(KEEP_SRC_TAG)"
	@echo "  IMAGES_DIR=$(IMAGES_DIR)"
	@echo ""
	@echo "Examples:"
	@echo "  make docker build IMAGE=myapp TAG=1.0.0"
	@echo "  make docker push REGISTRY=registry.local/library IMAGE=myapp TAG=1.0.0"
	@echo "  make docker save IMAGE=myapp TAG=1.0.0 COMPRESS=1"
	@echo "  make docker load ARCHIVE=images/myapp_1.0.0.tar.gz LOGIN=1 PUSH_OFFLINE=1"
	@echo "  make git commit    # спросит сообщение"
	@echo "  make git push      # commit+push с запросом сообщения"

# ======================
#   Служебные директории
# ======================
$(LOG_DIR):
	@mkdir -p $(LOG_DIR)

$(IMAGES_DIR):
	@mkdir -p $(IMAGES_DIR)

$(OFFLINE_DIR):
	@mkdir -p $(OFFLINE_DIR)

# ======================
#    Docker: реализация
# ======================
# Внутренние цели (не вызывать напрямую)
.PHONY: __docker_build __docker_check __docker_push __docker_login __docker_retag __docker_save __docker_load __docker_load_raw __docker_inspect __docker_create_builder __docker_use_builder __docker_clean __docker_print_config

# --- Команды сборки ---
define DOCKER_BUILD_CLASSIC
	docker build $(CACHE_FLAG) $(PULL_FLAG) $(QUIET_FLAG) \
	-f $(DOCKERFILE) -t "$(FULL_IMAGE)" \
	$(BUILD_ARGS) $(ARG_FILE_FLAGS) \
	"$(CONTEXT)"
endef

define DOCKER_BUILD_BUILDX
	docker buildx build $(CACHE_FLAG) $(PULL_FLAG) $(QUIET_FLAG) \
	$(if $(filter 1,$(PUSH)),--push,) \
	$(if $(filter 1,$(LOAD)),--load,) \
	--platform $(PLATFORMS) \
	-f $(DOCKERFILE) -t "$(FULL_IMAGE)" \
	$(BUILD_ARGS) $(ARG_FILE_FLAGS) \
	"$(CONTEXT)"
endef

__docker_build: $(LOG_DIR)
	@echo "$(COLOR_INFO)[INFO] Build: $(FULL_IMAGE)$(COLOR_RESET)"
	@echo "$(COLOR_INFO)[INFO] Log:   $(LOG_FILE)$(COLOR_RESET)"
	@{ \
	echo "=== Build started: $$(date) ==="; \
	if [ "$(USE_BUILDX)" = "1" ]; then \
	    $(DOCKER_BUILD_BUILDX); \
	else \
	    $(DOCKER_BUILD_CLASSIC); \
	fi; \
	echo "=== Build finished: $$(date) ==="; \
	} | tee $(LOG_FILE)
	@echo "$(COLOR_OK)[OK] Build success$(COLOR_RESET)"

__docker_check:
	@echo "$(COLOR_INFO)[INFO] Check (RUN_CMD) for $(FULL_IMAGE): $(RUN_CMD)$(COLOR_RESET)"
	@docker run --rm --entrypoint /bin/sh "$(FULL_IMAGE)" -lc "$(RUN_CMD)"

__docker_push:
	@if [ "$(USE_BUILDX)" != "1" ]; then \
	echo "$(COLOR_INFO)[INFO] Pushing: $(FULL_IMAGE)$(COLOR_RESET)"; \
	docker push "$(FULL_IMAGE)"; \
	else \
	echo "$(COLOR_WARN)[WARN] USE_BUILDX=1: push выполняется при buildx (PUSH=1).$(COLOR_RESET)"; \
	echo "$(COLOR_WARN)[WARN] Пересоберите с PUSH=1 или выполните docker push вручную.$(COLOR_RESET)"; \
	fi
	@echo "$(COLOR_OK)[OK] Push done$(COLOR_RESET)"

__docker_login:
	@if [ -z "$(REGISTRY)" ]; then \
	echo "$(COLOR_ERR)[ERR] REGISTRY не задан. Пример: make docker login REGISTRY=registry.local$(COLOR_RESET)"; \
	exit 1; \
	fi
	@echo "$(COLOR_INFO)[INFO] docker login $(REGISTRY)$(COLOR_RESET)"
	@docker login $(REGISTRY)

__docker_retag:
	@if [ -z "$(NEW_TAG)" ]; then \
	echo "$(COLOR_ERR)[ERR] Укажи NEW_TAG: make docker retag NEW_TAG=vX.Y.Z$(COLOR_RESET)"; \
	exit 1; \
	fi
	@echo "$(COLOR_INFO)[INFO] Retag: $(FULL_IMAGE) -> $(IMAGE):$(NEW_TAG)$(COLOR_RESET)"
	@docker tag "$(FULL_IMAGE)" "$(IMAGE):$(NEW_TAG)"
	@echo "$(COLOR_OK)[OK] Retag done$(COLOR_RESET)"

__docker_save: | $(IMAGES_DIR) $(OFFLINE_DIR)
	@src="$(SRC_IMAGE)"; \
	if [ -z "$$src" ]; then \
	if [ -n "$(IMAGE)" ]; then src="$(FULL_IMAGE)"; else echo "$(COLOR_ERR)[ERR] Укажи SRC_IMAGE или IMAGE/TAG$(COLOR_RESET)"; exit 1; fi; \
	fi; \
	docker image inspect "$$src" >/dev/null 2>&1 || { echo "$(COLOR_ERR)[ERR] Образ не найден: $$src$(COLOR_RESET)"; exit 1; }; \
	safe="$${src//\//_}"; safe="$${safe//:/_}"; out="$(IMAGES_DIR)/$$safe.tar"; \
	echo "$(COLOR_INFO)[INFO] Сохранение $$src -> $$out$(COLOR_RESET)"; \
	docker save -o "$$out" "$$src"; \
	if [ "$(COMPRESS)" = "1" ]; then \
	echo "$(COLOR_INFO)[INFO] Gzip $$out$(COLOR_RESET)"; gzip -f "$$out"; out="$$out.gz"; \
	fi; \
	if [ -n "$(SPLIT_SIZE)" ]; then \
	echo "$(COLOR_INFO)[INFO] Резка архива на чанки по $(SPLIT_SIZE)$(COLOR_RESET)"; \
	split -b $(SPLIT_SIZE) -d -a 3 "$$out" "$$out.part."; \
	fi; \
	{ sha256sum "$$out" 2>/dev/null || shasum -a 256 "$$out"; } > "$$out.sha256" 2>/dev/null || true; \
	echo "$$out" > "$(LAST_SAVE)"; \
	echo "$(COLOR_OK)[OK] Образ сохранён: $$out$(COLOR_RESET)"; \
	ls -lh "$$out"*

__docker_load_raw:
	@if [ -z "$(FILE)" ]; then \
	echo "$(COLOR_ERR)[ERR] Укажи FILE: make docker load-raw FILE=img.tar$(COLOR_RESET)"; \
	exit 1; \
	fi
	@echo "$(COLOR_INFO)[INFO] Loading image from $(FILE)$(COLOR_RESET)"
	@docker load -i "$(FILE)"
	@echo "$(COLOR_OK)[OK] Load done$(COLOR_RESET)"

__docker_load: | $(OFFLINE_DIR)
	@if [ ! -f "$(ARCHIVE)" ]; then \
	echo "$(COLOR_ERR)[ERR] Не найден архив: $(ARCHIVE) (ARCHIVE=... )$(COLOR_RESET)"; exit 1; \
	fi
	@echo "$(COLOR_INFO)[INFO] docker load -i $(ARCHIVE)$(COLOR_RESET)"
	@docker load -i "$(ARCHIVE)" | tee "$(LOAD_LOG)"
	@awk '/Loaded image:/ { last=$$0 } END { gsub(/^.*Loaded image: /,"",last); print last }' "$(LOAD_LOG)" > "$(SRC_FILE)"
	@src="$$(cat "$(SRC_FILE)" 2>/dev/null || true)"; \
	if [ -z "$$src" ]; then \
	echo "$(COLOR_ERR)[ERR] Не удалось определить repo:tag из docker load. Укажи IMAGE/TAG вручную.$(COLOR_RESET)"; \
	exit 1; \
	fi; \
	echo "$(COLOR_OK)[OK] Загружен исходный образ: $$src$(COLOR_RESET)"; \
	repo="$${src%:*}"; srctag="$${src#*:}"; [ "$$repo" = "$$srctag" ] && srctag="latest"; \
	src_name="$${repo##*/}"; \
	dst_image="$(IMAGE)"; [ -z "$$dst_image" ] && dst_image="$$src_name"; \
	if [ -z "$(IMAGE)" ] && [ "$(KEEP_SRC_TAG)" = "1" ]; then dst_tag="$$srctag"; else dst_tag="$(TAG)"; fi; \
	if [ -z "$(REGISTRY)" ]; then echo "$(COLOR_ERR)[ERR] REGISTRY пуст$(COLOR_RESET)"; exit 1; fi; \
	full_dst="$(REGISTRY)/$$dst_image:$$dst_tag"; echo "$$full_dst" > "$(DST_FILE)"; \
	echo "$(COLOR_INFO)[INFO] Тегируем: $$src -> $$full_dst$(COLOR_RESET)"; \
	docker tag "$$src" "$$full_dst"; \
	if [ "$(LOGIN)" = "1" ]; then echo "$(COLOR_INFO)[INFO] docker login $(REGISTRY)$(COLOR_RESET)"; docker login "$(REGISTRY)"; fi; \
	if [ "$(PUSH_OFFLINE)" = "1" ]; then echo "$(COLOR_INFO)[INFO] docker push $$full_dst$(COLOR_RESET)"; docker push "$$full_dst"; echo "$(COLOR_OK)[OK] Пуш завершён$(COLOR_RESET)"; else echo "$(COLOR_WARN)[WARN] PUSH_OFFLINE=0: пуш пропущен$(COLOR_RESET)"; fi

__docker_inspect:
	@docker image inspect "$(FULL_IMAGE)" || true

__docker_create_builder:
	@docker buildx create --name $(BUILDER) --use --bootstrap || docker buildx use $(BUILDER)
	@echo "$(COLOR_OK)[OK] Builder ready: $(BUILDER)$(COLOR_RESET)"

__docker_use_builder:
	@docker buildx use $(BUILDER)
	@docker buildx inspect --bootstrap
	@echo "$(COLOR_OK)[OK] Using builder: $(BUILDER)$(COLOR_RESET)"

__docker_clean:
	@echo "$(COLOR_WARN)[WARN] Removing logs and .offline$(COLOR_RESET)"
	@rm -rf "$(LOG_DIR)" "$(OFFLINE_DIR)"
	@echo "$(COLOR_OK)[OK] Clean done$(COLOR_RESET)"

__docker_print_config:
	@echo "FULL_IMAGE   : $(FULL_IMAGE)"
	@echo "REGISTRY     : $(REGISTRY)"
	@echo "IMAGE        : $(IMAGE)"
	@echo "TAG          : $(TAG)"
	@echo "CONTEXT      : $(CONTEXT)"
	@echo "DOCKERFILE   : $(DOCKERFILE)"
	@echo "USE_BUILDX   : $(USE_BUILDX)"
	@echo "PLATFORMS    : $(PLATFORMS)"
	@echo "PUSH         : $(PUSH)"
	@echo "LOAD         : $(LOAD)"
	@echo "NO_CACHE     : $(NO_CACHE)"
	@echo "PULL         : $(PULL)"
	@echo "QUIET        : $(QUIET)"
	@echo "BUILD_ARGS   : $(BUILD_ARGS)"
	@echo "ARG_FILE     : $(ARG_FILE)"
	@echo "RUN_CMD      : $(RUN_CMD)"
	@echo "BUILDER      : $(BUILDER)"
	@echo "LOG_DIR      : $(LOG_DIR)"
	@echo "LOG_FILE     : $(LOG_FILE)"

# ======================
#     Docker dispatcher
# ======================
.PHONY: docker
docker:
	@sub="$(word 2,$(MAKECMDGOALS))"; \
	case "$$sub" in \
	build)           $(MAKE) __docker_build ;; \
	check)           $(MAKE) __docker_check ;; \
	push)            $(MAKE) __docker_build __docker_check __docker_push ;; \
	save)            $(MAKE) __docker_save ;; \
	load)            $(MAKE) __docker_load ;; \
	load-raw)        $(MAKE) __docker_load_raw ;; \
	inspect)         $(MAKE) __docker_inspect ;; \
	retag)           $(MAKE) __docker_retag ;; \
	create-builder)  $(MAKE) __docker_create_builder ;; \
	use-builder)     $(MAKE) __docker_use_builder ;; \
	login)           $(MAKE) __docker_login ;; \
	clean)           $(MAKE) __docker_clean ;; \
	print-config)    $(MAKE) __docker_print_config ;; \
	"") \
	    echo "$(COLOR_INFO)[INFO] Docker commands:$(COLOR_RESET)"; \
	    echo "  make docker build           — собрать образ"; \
	    echo "  make docker check           — проверить контейнер (RUN_CMD)"; \
	    echo "  make docker push            — build + check + push"; \
	    echo "  make docker save            — сохранить образ в ./images/"; \
	    echo "  make docker load            — загрузить из архива и (опц.) запушить"; \
	    echo "  make docker load-raw        — чистый docker load -i FILE"; \
	    echo "  make docker inspect         — docker image inspect"; \
	    echo "  make docker retag           — перетегировать локальный образ"; \
	    echo "  make docker create-builder  — создать buildx builder"; \
	    echo "  make docker use-builder     — выбрать buildx builder"; \
	    echo "  make docker login           — docker login в REGISTRY"; \
	    echo "  make docker clean           — удалить логи и .offline"; \
	    echo "  make docker print-config    — показать конфигурацию"; \
	;; \
	*) echo "$(COLOR_ERR)[ERR] Unknown docker subcommand: $$sub$(COLOR_RESET)"; exit 1 ;; \
	esac

# ======================
#       Git: реализация
# ======================
.PHONY: __git_status __git_pull __git_commit __git_push __git_sync

__git_status:
	@echo "$(COLOR_INFO)[INFO] Git status ($(GIT_BRANCH))$(COLOR_RESET)"
	@git status -sb || echo "$(COLOR_WARN)[WARN] Not a git repository$(COLOR_RESET)"

__git_pull:
	@echo "$(COLOR_INFO)[INFO] Pulling from $(GIT_REMOTE)/$(GIT_BRANCH)$(COLOR_RESET)"
	@git pull $(GIT_REMOTE) $(GIT_BRANCH) || echo "$(COLOR_ERR)[ERR] git pull failed$(COLOR_RESET)"

__git_commit:
	@echo "$(COLOR_INFO)[INFO] Preparing commit$(COLOR_RESET)"
	@if [ -z "$(GIT_MSG)" ]; then \
	read -p "Введите сообщение коммита: " msg; \
	GIT_MSG="$$msg"; \
	else \
	GIT_MSG="$(GIT_MSG)"; \
	fi; \
	git add -A; \
	if git diff --cached --quiet; then \
	echo "$(COLOR_WARN)[WARN] Нет изменений для коммита$(COLOR_RESET)"; \
	else \
	git commit -m "$$GIT_MSG" && echo "$(COLOR_OK)[OK] Commit created: $$GIT_MSG$(COLOR_RESET)"; \
	fi

__git_push:
	@echo "$(COLOR_INFO)[INFO] Preparing push for $(GIT_BRANCH)$(COLOR_RESET)"
	@$(MAKE) __git_commit
	@echo "$(COLOR_INFO)[INFO] Pushing to $(GIT_REMOTE)/$(GIT_BRANCH)$(COLOR_RESET)"
	@git push $(GIT_REMOTE) $(GIT_BRANCH) || echo "$(COLOR_ERR)[ERR] git push failed$(COLOR_RESET)"

__git_sync:
	@echo "$(COLOR_INFO)[INFO] Git sync (pull + commit + push)$(COLOR_RESET)"
	@$(MAKE) __git_pull
	@$(MAKE) __git_push
	@echo "$(COLOR_OK)[OK] Git sync complete for branch $(GIT_BRANCH)$(COLOR_RESET)"

# ======================
#      Git dispatcher
# ======================
.PHONY: git
git:
	@sub="$(word 2,$(MAKECMDGOALS))"; \
	case "$$sub" in \
	status) $(MAKE) __git_status ;; \
	pull)   $(MAKE) __git_pull ;; \
	commit) $(MAKE) __git_commit ;; \
	push)   $(MAKE) __git_push ;; \
	sync)   $(MAKE) __git_sync ;; \
	"") \
	    echo "$(COLOR_INFO)[INFO] Git commands:$(COLOR_RESET)"; \
	    echo "  make git status   — показать статус"; \
	    echo "  make git pull     — получить изменения"; \
	    echo "  make git commit   — коммит (со вводом сообщения)"; \
	    echo "  make git push     — commit + push (со вводом сообщения)"; \
	    echo "  make git sync     — pull + commit + push"; \
	;; \
	*) echo "$(COLOR_ERR)[ERR] Unknown git subcommand: $$sub$(COLOR_RESET)"; exit 1 ;; \
	esac

.PHONY: status pull commit push sync \
	    build check save load load-raw inspect retag create-builder use-builder login clean print-config
status pull commit push sync \
build check save load load-raw inspect retag create-builder use-builder login clean print-config:
	@: