.PHONY: help bootstrap lint test check dev datascience inventory inventory-all local servers workstations clean status tailscale tailscale-check tailscale-dev tailscale-status create-vault create-vm monitoring copy-ssh-key copy-ssh-keys copy-ssh-keys-ansible copy-ssh-key-mailcow bootstrap-root-ssh bootstrap-root-ssh-services bootstrap-root-ssh-failed mailcow-mailbox mailcow-create-alerts vault-import-env
.DEFAULT_GOAL := help

## Colors for output
BOLD := \033[1m
RED := \033[31m
GREEN := \033[32m
YELLOW := \033[33m
BLUE := \033[34m
RESET := \033[0m

# Playbook paths
PLAYBOOK_SITE := playbooks/site.yml
PLAYBOOK_DEV := playbooks/development.yml
PLAYBOOK_LOCAL := playbooks/local.yml
PLAYBOOK_SERVERS := playbooks/servers.yml
PLAYBOOK_WORKSTATIONS := playbooks/workstations.yml
PLAYBOOK_MAINTENANCE := playbooks/maintenance.yml
PLAYBOOK_TAILSCALE := playbooks/tailscale.yml
PLAYBOOK_PROXMOX := playbooks/infrastructure/proxmox-vm.yml
PLAYBOOK_PROXMOX_INFO := playbooks/app/proxmox_info.yml

# Collection and requirement paths
COLLECTIONS_REQ := collections/requirements.yml
PYTHON_REQ := requirements.txt

# Inventory paths
INVENTORY := inventories/production
INVENTORY_HOSTS := $(INVENTORY)/hosts

# Python venv (created by `make bootstrap`)
VENV := .venv
ifneq ($(wildcard $(VENV)/bin/ansible-playbook),)
export PATH := $(abspath $(VENV)/bin):$(PATH)
ANSIBLE_VAULT := $(abspath $(VENV))/bin/ansible-vault
else
ANSIBLE_VAULT := ansible-vault
endif

# Common ansible-playbook command with options
ANSIBLE_PLAYBOOK := ansible-playbook -i $(INVENTORY)
ANSIBLE_ARGS := --vault-password-file ~/.ansible-vault-pass
# Note: sudo passwords are in vault files as ansible_become_password

## Auto-detect current host to exclude from remote operations
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
CURRENT_IP := $(shell ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null || echo "")
else
CURRENT_IP := $(shell hostname -I 2>/dev/null | awk '{print $$1}')
endif
# NOTE: inventory parsing may require vault secrets. Keep this best-effort and silent in CI.
CURRENT_HOST := $(shell ansible-inventory --list --vault-password-file ~/.ansible-vault-pass 2>/dev/null | jq -r '._meta.hostvars | to_entries[] | select(.value.ansible_host == "$(CURRENT_IP)") | .key' 2>/dev/null | head -1)
EXCLUDE_CURRENT := $(if $(CURRENT_HOST),--limit '!$(CURRENT_HOST)',)

help: ## Show this help message
	@echo "$(BOLD)Ansible Development Environment$(RESET)"
	@echo ""
	@echo "$(BOLD)Available commands:$(RESET)"
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "  $(BLUE)%-15s$(RESET) %s\n", $$1, $$2}'
	@echo ""
	@echo "$(BOLD)Examples:$(RESET)"
	@echo "  make bootstrap               # Set up dependencies"
	@echo "  make check                   # Dry run all hosts"
	@echo "  make apply                   # Run on all dev hosts"
	@echo "  make dev HOST=dev01          # Run on specific host"
	@echo "  make local                   # Run local playbook"
	@echo "  make maintenance                    # Run maintenance on all hosts"
	@echo "  make maintenance GROUP=dev          # Run maintenance on dev group"
	@echo "  make maintenance HOST=dev01         # Run maintenance on specific host"
	@echo "  make maintenance CHECK=true         # Dry-run maintenance on all hosts"
	@echo "  make maintenance VERBOSE=true       # Run with verbose output"
	@echo "  make maintenance-verbose GROUP=dev  # Verbose maintenance on dev group"
	@echo ""

require-ansible: ## Verify ansible is available (run make bootstrap if missing)
	@command -v ansible-playbook >/dev/null 2>&1 && command -v ansible-vault >/dev/null 2>&1 || { \
		echo "$(RED)ansible-playbook/ansible-vault not found$(RESET)"; \
		echo "Run: $(BLUE)make bootstrap$(RESET)"; \
		exit 1; \
	}

bootstrap: ## Install all project dependencies from requirements files
	@echo "$(BOLD)Installing Project Dependencies$(RESET)"
	@echo ""
	@echo "$(YELLOW)Python venv ($(VENV))/$(PYTHON_REQ):$(RESET)"
	@if [ ! -f "$(PYTHON_REQ)" ]; then \
		printf "  %-30s " "$(PYTHON_REQ)"; \
		echo "$(RED)✗ File not found$(RESET)"; \
	elif ! command -v python3 >/dev/null 2>&1; then \
		printf "  %-30s " "Python venv"; \
		echo "$(RED)✗ python3 not found$(RESET)"; \
	else \
		if [ ! -d "$(VENV)" ]; then \
			printf "  %-30s " "Creating venv"; \
			python3 -m venv "$(VENV)" && echo "$(GREEN)✓ Created$(RESET)" || { echo "$(RED)✗ Failed$(RESET)"; exit 1; }; \
		fi; \
		printf "  %-30s " "Installing packages"; \
		if "$(VENV)/bin/pip" install -r "$(PYTHON_REQ)" >/dev/null 2>&1; then \
			echo "$(GREEN)✓ Installed$(RESET)"; \
			echo "  $(BLUE)Ansible:$(RESET) $(abspath $(VENV))/bin/ansible-playbook"; \
		else \
			echo "$(RED)✗ Failed$(RESET)"; \
			exit 1; \
		fi; \
	fi
	@echo ""
	@echo "$(YELLOW)Node.js Dependencies (package.json):$(RESET)"
	@if [ -f "package.json" ] && command -v npm >/dev/null 2>&1; then \
		printf "  %-30s " "Installing npm packages"; \
		if npm install >/dev/null 2>&1; then \
			echo "$(GREEN)✓ Installed$(RESET)"; \
		else \
			echo "$(RED)✗ Failed$(RESET)"; \
		fi; \
	else \
		printf "  %-30s " "npm packages"; \
		echo "$(YELLOW)⚠ Skipped (package.json or npm not found)$(RESET)"; \
	fi
	@echo ""
	@echo "$(YELLOW)Ansible Collections ($(COLLECTIONS_REQ)):$(RESET)"
	@if [ -f "$(COLLECTIONS_REQ)" ]; then \
		GALAXY="$$(command -v ansible-galaxy)"; \
		[ -x "$(VENV)/bin/ansible-galaxy" ] && GALAXY="$(abspath $(VENV))/bin/ansible-galaxy"; \
		"$$GALAXY" collection install -r $(COLLECTIONS_REQ) 2>&1 | grep -E "(Installing|Skipping|ERROR)" | while read line; do \
			if echo "$$line" | grep -q "Installing"; then \
				collection=$$(echo "$$line" | awk '{print $$2}' | sed 's/:.*//'); \
				printf "  $(GREEN)✓ %-30s$(RESET) Installed\n" "$$collection"; \
			elif echo "$$line" | grep -q "Skipping"; then \
				collection=$$(echo "$$line" | awk '{print $$2}' | sed 's/,.*//'); \
				printf "  $(BLUE)- %-30s$(RESET) Already installed\n" "$$collection"; \
			elif echo "$$line" | grep -q "ERROR"; then \
				printf "  $(RED)✗ Error: $$line$(RESET)\n"; \
			fi; \
		done || "$$GALAXY" collection install -r $(COLLECTIONS_REQ); \
	else \
		printf "  %-30s " "$(COLLECTIONS_REQ)"; \
		echo "$(RED)✗ File not found$(RESET)"; \
	fi
	@echo ""

lint: ## Run ansible-lint on all playbooks and roles
	@echo "$(YELLOW)Running ansible-lint...$(RESET)"
	ansible-lint
	@echo "$(GREEN)✓ Linting completed$(RESET)"

test-syntax: ## Run comprehensive syntax and validation checks
	@echo "$(BOLD)Comprehensive Testing$(RESET)"
	@echo ""
	@echo "$(YELLOW)Playbook Syntax:$(RESET)"
	@for playbook in $(PLAYBOOK_DEV) $(PLAYBOOK_LOCAL) $(PLAYBOOK_MAINTENANCE) $(PLAYBOOK_TAILSCALE); do \
		if [ -f "$$playbook" ]; then \
			printf "  %-25s " "$$playbook"; \
			if ansible-playbook "$$playbook" --syntax-check >/dev/null 2>&1; then \
				echo "$(GREEN)✓ OK$(RESET)"; \
			else \
				echo "$(RED)✗ FAIL$(RESET)"; \
			fi; \
		fi; \
	done
	@echo ""
	@echo "$(YELLOW)Infrastructure Playbooks:$(RESET)"
	@for playbook in playbooks/infrastructure/*.yml; do \
		if [ -f "$$playbook" ]; then \
			printf "  %-25s " "$$playbook"; \
			if ansible-playbook "$$playbook" --syntax-check >/dev/null 2>&1; then \
				echo "$(GREEN)✓ OK$(RESET)"; \
			else \
				echo "$(RED)✗ FAIL$(RESET)"; \
			fi; \
		fi; \
	done
	@echo ""
	@echo "$(YELLOW)App Project Playbooks:$(RESET)"
	@for playbook in playbooks/app/site.yml playbooks/app/provision_vms.yml playbooks/app/configure_app.yml playbooks/app/ssh_client_config.yml; do \
		if [ -f "$$playbook" ]; then \
			printf "  %-25s " "$$playbook"; \
			if ansible-playbook "$$playbook" --syntax-check >/dev/null 2>&1; then \
				echo "$(GREEN)✓ OK$(RESET)"; \
			else \
				echo "$(RED)✗ FAIL$(RESET)"; \
			fi; \
		fi; \
	done
	@echo ""
	@echo "$(YELLOW)Role Test Playbooks:$(RESET)"
	@for test_playbook in roles/*/tests/test.yml; do \
		if [ -f "$$test_playbook" ]; then \
			role_name=$$(echo "$$test_playbook" | cut -d'/' -f2); \
			printf "  %-25s " "roles/$$role_name/test"; \
			if ansible-playbook "$$test_playbook" --syntax-check >/dev/null 2>&1; then \
				echo "$(GREEN)✓ OK$(RESET)"; \
			else \
				echo "$(RED)✗ FAIL$(RESET)"; \
			fi; \
		fi; \
	done
	@echo ""
	@echo "$(YELLOW)Markdown Validation:$(RESET)"
	@if [ -f "package.json" ] && command -v npm >/dev/null 2>&1; then \
		printf "  %-25s " "Markdown syntax"; \
		if npm run test:markdown >/dev/null 2>&1; then \
			echo "$(GREEN)✓ OK$(RESET)"; \
		else \
			echo "$(YELLOW)⚠ Issues$(RESET)"; \
		fi; \
	else \
		printf "  %-25s " "Markdown syntax"; \
		echo "$(YELLOW)⚠ npm/package.json not available$(RESET)"; \
	fi
	@echo ""
	@echo "$(YELLOW)Documentation Links:$(RESET)"
	@if [ -f "package.json" ] && command -v npm >/dev/null 2>&1; then \
		printf "  %-25s " "Link validation"; \
		if npm run test:links >/dev/null 2>&1; then \
			echo "$(GREEN)✓ OK$(RESET)"; \
		else \
			echo "$(YELLOW)⚠ Issues$(RESET)"; \
		fi; \
	else \
		printf "  %-25s " "Link validation"; \
		echo "$(YELLOW)⚠ npm/package.json not available$(RESET)"; \
	fi
	@echo ""
	@echo "$(YELLOW)Configuration Validation:$(RESET)"
	@for yaml_file in inventories/production/group_vars/all/main.yml; do \
		if [ -f "$$yaml_file" ]; then \
			printf "  %-25s " "$$yaml_file (YAML)"; \
			if python3 -c "import yaml" >/dev/null 2>&1; then \
				if python3 -c "import yaml; yaml.safe_load(open('$$yaml_file'))" >/dev/null 2>&1; then \
					echo "$(GREEN)✓ OK$(RESET)"; \
				else \
					echo "$(RED)✗ FAIL$(RESET)"; \
				fi; \
			else \
				echo "$(YELLOW)⚠ Skipped (PyYAML not installed)$(RESET)"; \
			fi; \
		fi; \
	done
	@printf "  %-25s " "ansible.cfg (INI)"; \
	if python3 -c "import configparser; c=configparser.ConfigParser(); c.read('ansible.cfg')" >/dev/null 2>&1; then \
		echo "$(GREEN)✓ OK$(RESET)"; \
	else \
		echo "$(RED)✗ FAIL$(RESET)"; \
	fi
	@echo ""

test: ## Run all tests (lint + syntax check if available)
	@if command -v ansible-lint >/dev/null 2>&1; then \
		echo "$(YELLOW)Running ansible-lint...$(RESET)"; \
		ansible-lint 2>/dev/null && echo "$(GREEN)✓ Linting completed$(RESET)" || echo "$(YELLOW)⚠ Linting had issues$(RESET)"; \
		echo ""; \
	fi
	@$(MAKE) test-syntax

check: auto-fallback ## Dry-run the development playbook (--check mode)
	@echo "$(YELLOW)Running dry-run on development hosts...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --check --diff

check-local: ## Dry-run the local playbook
	@echo "$(YELLOW)Running dry-run on localhost...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_LOCAL) --check --diff -K

site: ## Run the complete site playbook
	@echo "$(YELLOW)Running complete site deployment...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_SITE)

local: ## Run the local playbook on localhost
	@echo "$(YELLOW)Applying local playbook...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_LOCAL) -K

servers: ## Run baseline server playbook (usage: make servers [GROUP=services] [HOST=host1])
	@echo "$(YELLOW)Applying server baseline...$(RESET)"
	@EXTRA=""; \
	if [ -n "$(HOST)" ]; then \
		$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_SERVERS) --limit $(HOST); \
	elif [ -n "$(GROUP)" ]; then \
		$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_SERVERS) -e target_group=$(GROUP); \
	else \
		$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_SERVERS); \
	fi

caddy-auth: require-ansible ## Ensure auth.levkin.ca reverse proxy on Caddy VM
	@echo "$(YELLOW)Updating Caddy for Authentik...$(RESET)"
	$(ANSIBLE_PLAYBOOK) playbooks/caddy-auth-authentik.yml $(ANSIBLE_ARGS)

caddy-levkin: require-ansible ## Ensure levkin.ca reverse proxy on Caddy VM
	@echo "$(YELLOW)Updating Caddy for levkin.ca...$(RESET)"
	$(ANSIBLE_PLAYBOOK) playbooks/caddy-levkin-site.yml $(ANSIBLE_ARGS)

workstations: ## Run workstation baseline (usage: make workstations [GROUP=dev] [HOST=dev01])
	@echo "$(YELLOW)Applying workstation baseline...$(RESET)"
	@EXTRA=""; \
	if [ -n "$(HOST)" ]; then \
		$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_WORKSTATIONS) --limit $(HOST); \
	elif [ -n "$(GROUP)" ]; then \
		$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_WORKSTATIONS) -e target_group=$(GROUP); \
	else \
		$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_WORKSTATIONS); \
	fi

# Host-specific targets
dev: ## Run on specific host (usage: make dev HOST=dev01 [SUDO=true] [SSH_PASS=true])
ifndef HOST
	@echo "$(RED)Error: HOST parameter required$(RESET)"
	@echo "Usage: make dev HOST=dev01 [SUDO=true] [SSH_PASS=true]"
	@exit 1
endif
	@echo "$(YELLOW)Running on host: $(HOST)$(RESET)"
	@SSH_FLAGS=""; \
	SUDO_FLAGS=""; \
	if [ "$(SSH_PASS)" = "true" ]; then \
		SSH_FLAGS="-k"; \
	fi; \
	if [ "$(SUDO)" = "true" ]; then \
		SUDO_FLAGS="-K"; \
	fi; \
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --limit $(HOST) $(ANSIBLE_ARGS) $$SSH_FLAGS $$SUDO_FLAGS

# Data science role
datascience: ## Install data science stack (usage: make datascience HOST=server01)
ifndef HOST
	@echo "$(RED)Error: HOST parameter required$(RESET)"
	@echo "Usage: make datascience HOST=server01"
	@exit 1
endif
	@echo "$(YELLOW)Installing data science stack on: $(HOST)$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --limit $(HOST) --tags datascience

# Inventory system
inventory: ## Show installed software on host (usage: make inventory HOST=dev01)
ifndef HOST
	@echo "$(RED)Error: HOST parameter required$(RESET)"
	@echo "Usage: make inventory HOST=dev01"
	@exit 1
endif
	@echo "$(BOLD)Software Inventory for: $(HOST)$(RESET)"
	@echo ""
	@ip=$$(ansible-inventory --list | jq -r "._meta.hostvars.$(HOST).ansible_host // empty" 2>/dev/null); \
	user=$$(ansible-inventory --list | jq -r "._meta.hostvars.$(HOST).ansible_user // empty" 2>/dev/null); \
	if [ -n "$$ip" ] && [ "$$ip" != "null" ] && [ -n "$$user" ] && [ "$$user" != "null" ]; then \
		scp -q scripts/inventory.sh $$user@$$ip:/tmp/inventory.sh 2>/dev/null && \
		ssh $$user@$$ip 'bash /tmp/inventory.sh; rm /tmp/inventory.sh' 2>/dev/null; \
	else \
		echo "$(RED)Could not determine IP or user for $(HOST)$(RESET)"; \
	fi

inventory-all: ## Show installed software on all hosts
	@echo "$(BOLD)Software Inventory - All Hosts$(RESET)\n"
	@for host in $$(ansible all --list-hosts 2>/dev/null | grep -v "hosts" | tr -d ' '); do \
		echo "$(YELLOW)=== $$host ===$(RESET)"; \
		ansible $$host -m script -a "scripts/inventory.sh" 2>/dev/null | sed -n '/CHANGED/,$$p' | tail -n +3 || echo "$(RED)Failed to connect$(RESET)"; \
		echo ""; \
	done

# Tag-based execution
security: ## Run only security-related roles
	@echo "$(YELLOW)Running security roles...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --tags security

# Unified maintenance target with intelligent parameter detection
maintenance: ## Run maintenance (usage: make maintenance [GROUP=dev] [HOST=dev01] [SERIAL=1] [CHECK=true] [VERBOSE=true])
	@$(MAKE) _maintenance-run

_maintenance-run:
	@# Determine target and build command
	@TARGET="all"; \
	ANSIBLE_CMD="$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_MAINTENANCE)"; \
	DESCRIPTION="all hosts"; \
	NEED_SUDO=""; \
	\
	if [ -n "$(HOST)" ]; then \
		TARGET="host $(HOST)"; \
		ANSIBLE_CMD="$$ANSIBLE_CMD --limit $(HOST)"; \
		DESCRIPTION="host $(HOST)"; \
		if [ "$(HOST)" = "localhost" ]; then \
			NEED_SUDO="-K"; \
		fi; \
	elif [ -n "$(GROUP)" ]; then \
		TARGET="$(GROUP) group"; \
		ANSIBLE_CMD="$$ANSIBLE_CMD -e target_group=$(GROUP)"; \
		DESCRIPTION="$(GROUP) group"; \
		if [ "$(GROUP)" = "local" ]; then \
			NEED_SUDO="-K"; \
		fi; \
	else \
		NEED_SUDO="-K"; \
	fi; \
	\
	if [ -n "$(SERIAL)" ]; then \
		ANSIBLE_CMD="$$ANSIBLE_CMD -e maintenance_serial=$(SERIAL)"; \
		DESCRIPTION="$$DESCRIPTION (serial=$(SERIAL))"; \
	fi; \
	\
	if [ "$(CHECK)" = "true" ]; then \
		ANSIBLE_CMD="$$ANSIBLE_CMD --check --diff"; \
		echo "$(YELLOW)Dry-run maintenance on $$DESCRIPTION...$(RESET)"; \
	else \
		echo "$(YELLOW)Running maintenance on $$DESCRIPTION...$(RESET)"; \
	fi; \
	\
	if [ "$(VERBOSE)" = "true" ]; then \
		ANSIBLE_CMD="$$ANSIBLE_CMD -v"; \
		echo "$(BLUE)Running with verbose output...$(RESET)"; \
	fi; \
	\
	if [ -n "$(GROUP)" ] && [ "$(GROUP)" != "dev" ] && [ "$(GROUP)" != "local" ]; then \
		echo "$(BLUE)Available groups: dev, gitea, portainer, homepage, ansible, local$(RESET)"; \
	fi; \
	\
	$$ANSIBLE_CMD $$NEED_SUDO

# Legacy/convenience aliases
maintenance-dev: ## Run maintenance on dev group (legacy alias)
	@$(MAKE) maintenance GROUP=dev

maintenance-all: ## Run maintenance on all hosts (legacy alias)
	@$(MAKE) maintenance

maintenance-check: ## Dry-run maintenance (legacy alias, usage: make maintenance-check [GROUP=dev])
	@$(MAKE) maintenance CHECK=true GROUP=$(GROUP)

maintenance-verbose: ## Run maintenance with verbose output (usage: make maintenance-verbose [GROUP=dev])
	@$(MAKE) maintenance VERBOSE=true GROUP=$(GROUP)

docker: ## Install/configure Docker only
	@echo "$(YELLOW)Running Docker setup...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --tags docker

shell: ## Configure shell (usage: make shell [HOST=dev02] [SUDO=true])
ifdef HOST
	@echo "$(YELLOW)Running shell configuration on host: $(HOST)$(RESET)"
	@if [ "$(SUDO)" = "true" ]; then \
		$(ANSIBLE_PLAYBOOK) playbooks/shell.yml --limit $(HOST) $(ANSIBLE_ARGS) -K; \
	else \
		$(ANSIBLE_PLAYBOOK) playbooks/shell.yml --limit $(HOST) $(ANSIBLE_ARGS); \
	fi
else
	@echo "$(YELLOW)Running shell configuration on all dev hosts...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --tags shell
endif

shell-all: ## Configure shell on all hosts (usage: make shell-all)
	@echo "$(YELLOW)Running shell configuration on all hosts...$(RESET)"
	$(ANSIBLE_PLAYBOOK) playbooks/shell.yml $(ANSIBLE_ARGS)

apps: ## Install applications only
	@echo "$(YELLOW)Installing applications...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_WORKSTATIONS) --tags apps

# Connectivity targets
ping: require-ansible auto-fallback ## Ping hosts with colored output (usage: make ping [GROUP=dev] [HOST=dev01])
ifdef HOST
	@echo "$(YELLOW)Pinging host: $(HOST)$(RESET)"
	@ansible $(HOST) -m ping --one-line | while read line; do \
		if echo "$$line" | grep -q "SUCCESS"; then \
			echo "$(GREEN)✓ $$line$(RESET)"; \
		elif echo "$$line" | grep -q "UNREACHABLE"; then \
			echo "$(RED)✗ $$line$(RESET)"; \
		else \
			echo "$(YELLOW)? $$line$(RESET)"; \
		fi; \
	done
else ifdef GROUP
	@echo "$(YELLOW)Pinging $(GROUP) group...$(RESET)"
	@ansible $(GROUP) -m ping --one-line | while read line; do \
		if echo "$$line" | grep -q "SUCCESS"; then \
			echo "$(GREEN)✓ $$line$(RESET)"; \
		elif echo "$$line" | grep -q "UNREACHABLE"; then \
			echo "$(RED)✗ $$line$(RESET)"; \
		else \
			echo "$(YELLOW)? $$line$(RESET)"; \
		fi; \
	done
else
	@echo "$(YELLOW)Pinging all hosts...$(RESET)"
	@if [ -n "$(CURRENT_HOST)" ]; then \
		echo "$(BLUE)Auto-excluding current host: $(CURRENT_HOST) ($(CURRENT_IP))$(RESET)"; \
	fi
	@echo ""
	@ansible all -m ping --one-line $(EXCLUDE_CURRENT) 2>/dev/null | grep -E "(SUCCESS|UNREACHABLE)" > /tmp/ping_results.tmp; \
	success_count=$$(grep -c "SUCCESS" /tmp/ping_results.tmp 2>/dev/null || echo 0); \
	fail_count=$$(grep -c "UNREACHABLE" /tmp/ping_results.tmp 2>/dev/null || echo 0); \
	while read line; do \
		host=$$(echo "$$line" | cut -d' ' -f1); \
		if echo "$$line" | grep -q "SUCCESS"; then \
			printf "$(GREEN)  ✓ %-20s$(RESET) Connected\n" "$$host"; \
		elif echo "$$line" | grep -q "UNREACHABLE"; then \
			reason=$$(echo "$$line" | grep -o "Permission denied.*" | head -1 || echo "Connection failed"); \
			printf "$(RED)  ✗ %-20s$(RESET) $$reason\n" "$$host"; \
		fi; \
	done < /tmp/ping_results.tmp; \
	rm -f /tmp/ping_results.tmp; \
	echo ""; \
	printf "$(BOLD)Summary:$(RESET) $(GREEN)$$success_count connected$(RESET), $(RED)$$fail_count failed$(RESET)\n"
endif

facts: ## Gather facts from all hosts
	@echo "$(YELLOW)Gathering facts...$(RESET)"
	ansible all -m setup --tree /tmp/facts $(EXCLUDE_CURRENT)

show-current: ## Show current host that will be auto-excluded
	@echo "$(BOLD)Current Host Detection:$(RESET)"
	@echo "  Current IP: $(BLUE)$(CURRENT_IP)$(RESET)"
	@echo "  Current Host: $(BLUE)$(CURRENT_HOST)$(RESET)"
	@echo "  Exclude Flag: $(BLUE)$(EXCLUDE_CURRENT)$(RESET)"

clean: ## Clean up ansible artifacts
	@echo "$(YELLOW)Cleaning up artifacts...$(RESET)"
	rm -rf .ansible/facts/
	find . -name "*.retry" -delete
	@echo "$(GREEN)✓ Cleanup completed$(RESET)"

# Debug targets
debug: ## Run with debug output enabled
	@echo "$(YELLOW)Running with debug output...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) -e "ansible_debug_output=true"

verbose: ## Run with verbose output
	@echo "$(YELLOW)Running with verbose output...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) -vv

# Quick development workflow
quick: test check ## Quick test and check before applying
	@echo "$(GREEN)✓ Ready to apply changes$(RESET)"

# Tailscale management
tailscale: ## Install Tailscale on all machines
	@echo "$(YELLOW)Installing Tailscale on all machines...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_TAILSCALE)
	@echo "$(GREEN)✓ Tailscale installation complete$(RESET)"

tailscale-check: ## Check Tailscale installation (dry-run)
	@echo "$(YELLOW)Checking Tailscale installation...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_TAILSCALE) --check --diff
	@echo "$(GREEN)✓ Tailscale check complete$(RESET)"

tailscale-dev: ## Install Tailscale on dev machines only
	@echo "$(YELLOW)Installing Tailscale on dev machines...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_TAILSCALE) --limit dev
	@echo "$(GREEN)✓ Tailscale installation on dev machines complete$(RESET)"

tailscale-status: ## Check Tailscale status on all machines
	@echo "$(BOLD)Tailscale Status$(RESET)"
	@if [ -n "$(CURRENT_HOST)" ]; then \
		echo "$(BLUE)Auto-excluding current host: $(CURRENT_HOST) ($(CURRENT_IP))$(RESET)"; \
	fi
	@echo ""
	@ansible all  -m shell -a "tailscale status --json | jq -r '.Self.DNSName + \" (\" + .Self.TailscaleIPs[0] + \") - \" + .BackendState'" --become $(EXCLUDE_CURRENT) 2>/dev/null | while read line; do \
		host=$$(echo "$$line" | cut -d' ' -f1); \
		status=$$(echo "$$line" | grep -o "Running\|Stopped\|NeedsLogin" || echo "Unknown"); \
		ip=$$(echo "$$line" | grep -o "100\.[0-9.]*" || echo "No IP"); \
		if echo "$$line" | grep -q "SUCCESS"; then \
			result=$$(echo "$$line" | cut -d'>' -f2 | tr -d ' "'); \
			printf "  $(GREEN)✓ %-20s$(RESET) %s\n" "$$host" "$$result"; \
		elif echo "$$line" | grep -q "FAILED\|UNREACHABLE"; then \
			printf "  $(RED)✗ %-20s$(RESET) Not available\n" "$$host"; \
		fi; \
	done || ansible all  -m shell -a "tailscale status" --become $(EXCLUDE_CURRENT) 2>/dev/null | grep -E "(SUCCESS|FAILED)" | while read line; do \
		host=$$(echo "$$line" | cut -d' ' -f1); \
		if echo "$$line" | grep -q "SUCCESS"; then \
			printf "  $(GREEN)✓ %-20s$(RESET) Connected\n" "$$host"; \
		else \
			printf "  $(RED)✗ %-20s$(RESET) Failed\n" "$$host"; \
		fi; \
	done

# Vault management
edit-vault: require-ansible ## Edit encrypted host vars (usage: make edit-vault HOST=KrakenMint)
ifndef HOST
	@echo "$(RED)Error: HOST parameter required$(RESET)"
	@echo "Usage: make edit-vault HOST=KrakenMint"
	@exit 1
endif
	@vault_file="$(INVENTORY)/host_vars/$(HOST)/vault.yml"; \
	if [ ! -f "$$vault_file" ]; then vault_file="$(INVENTORY)/host_vars/$(HOST).yml"; fi; \
	if [ ! -f "$$vault_file" ]; then \
		echo "$(RED)No vault file for $(HOST):$(RESET)"; \
		echo "  $(INVENTORY)/host_vars/$(HOST)/vault.yml"; \
		echo "  $(INVENTORY)/host_vars/$(HOST).yml"; \
		exit 1; \
	fi; \
	echo "$(BLUE)Editing $$vault_file$(RESET)"; \
	$(ANSIBLE_VAULT) edit "$$vault_file"

edit-group-vault: require-ansible ## Edit encrypted group vars (usage: make edit-group-vault)
	$(ANSIBLE_VAULT) edit $(INVENTORY)/group_vars/all/vault.yml


copy-ssh-key: ## Copy SSH key to specific host (usage: make copy-ssh-key HOST=giteaVM)
ifndef HOST
	@echo "$(RED)Error: HOST parameter required$(RESET)"
	@echo "Usage: make copy-ssh-key HOST=giteaVM"
	@exit 1
endif
	@echo "$(YELLOW)Copying SSH key to $(HOST)...$(RESET)"
	@ip=$$(ansible-inventory -i $(INVENTORY) $(ANSIBLE_ARGS) --list 2>/dev/null | jq -r --arg h "$(HOST)" '._meta.hostvars[$$h].ansible_host // empty'); \
	user=$$(ansible-inventory -i $(INVENTORY) $(ANSIBLE_ARGS) --list 2>/dev/null | jq -r --arg h "$(HOST)" '._meta.hostvars[$$h].ansible_user // empty'); \
	if [ -z "$$ip" ] || [ "$$ip" = "null" ]; then \
		ip=$$(awk -v h="$(HOST)" '$$1==h {print $$2}' $(INVENTORY_HOSTS) | sed 's/ansible_host=//'); \
	fi; \
	if [ -z "$$user" ] || [ "$$user" = "null" ]; then \
		user=$$(awk -v h="$(HOST)" '$$1==h {for(i=2;i<=NF;i++) if($$i~/^ansible_user=/) {sub(/ansible_user=/,"",$$i); print $$i; exit}}' $(INVENTORY_HOSTS)); \
	fi; \
	if [ -n "$$ip" ] && [ -n "$$user" ]; then \
		echo "Target: $$user@$$ip"; \
		ssh-copy-id -i "$${SSH_PUBLIC_KEY:-$$HOME/.ssh/id_ed25519.pub}" "$$user@$$ip"; \
	else \
		echo "$(RED)Could not determine IP or user for $(HOST)$(RESET)"; \
		echo "Check your inventory and host_vars"; \
		exit 1; \
	fi

copy-ssh-keys: ## Copy SSH key to all inventory hosts (usage: make copy-ssh-keys [GROUP=services])
	@echo "$(YELLOW)Copying SSH key to inventory hosts...$(RESET)"
	@echo "Using key: $${SSH_PUBLIC_KEY:-$$HOME/.ssh/id_ed25519.pub}"
	@echo "$(YELLOW)You will be prompted for each host's password (last time).$(RESET)"
	@failed=0; ok=0; \
	if [ -n "$(GROUP)" ]; then \
		hosts=$$(ansible-inventory -i $(INVENTORY) $(ANSIBLE_ARGS) --list 2>/dev/null | jq -r ".\"$(GROUP)\".hosts[]? // empty"); \
	else \
		hosts=$$(ansible-inventory -i $(INVENTORY) $(ANSIBLE_ARGS) --list 2>/dev/null | jq -r '._meta.hostvars | keys[]' | grep -v '^localhost$$' | sort); \
	fi; \
	if [ -z "$$hosts" ]; then \
		if [ -n "$(GROUP)" ]; then \
			hosts=$$(awk -v g="$(GROUP)" 'BEGIN{ing=0} /^\[/ {ing=($$0=="["g"]"); next} ing && /^[a-zA-Z]/ {print $$1}' $(INVENTORY_HOSTS)); \
		else \
			hosts=$$(awk '/^\[/ {next} /^[a-zA-Z]/ && $$1!="localhost" {print $$1}' $(INVENTORY_HOSTS)); \
		fi; \
	fi; \
	for host in $$hosts; do \
		echo ""; echo "$(BLUE)==> $$host$(RESET)"; \
		if $(MAKE) --no-print-directory copy-ssh-key HOST=$$host; then ok=$$((ok+1)); else failed=$$((failed+1)); fi; \
	done; \
	echo ""; \
	echo "$(GREEN)Done: $$ok succeeded$(RESET), $(RED)$$failed failed$(RESET)"; \
	[ $$failed -eq 0 ]

copy-ssh-keys-ansible: require-ansible ## Copy SSH key via Ansible (usage: make copy-ssh-keys-ansible [GROUP=services] [HOST=dev01])
	@echo "$(YELLOW)Deploying SSH key with Ansible (may prompt for SSH password)...$(RESET)"
	@limit="all:!local"; \
	[ -n "$(GROUP)" ] && limit="$(GROUP)"; \
	[ -n "$(HOST)" ] && limit="$(HOST)"; \
	$(ANSIBLE_PLAYBOOK) playbooks/ssh-keys.yml $(ANSIBLE_ARGS) --limit "$$limit" --ask-pass

copy-ssh-key-mailcow: ## Copy SSH key to Mailcow VM (root@10.0.10.132 on pve201; prompts for root password once)
	@$(MAKE) --no-print-directory copy-ssh-key HOST=mailcow

bootstrap-root-ssh-caddy: ## Bootstrap root on caddy via su + vault_lxc_root_password
	@chmod +x scripts/bootstrap-root-ssh-su-password.sh scripts/load-vault-lxc-root-password.sh
	@. scripts/load-vault-lxc-root-password.sh; ./scripts/bootstrap-root-ssh-su-password.sh caddy

bootstrap-root-ssh: ## SSH as ladmin, su to root, install root key (usage: make bootstrap-root-ssh HOST=listmonk)
ifndef HOST
	@echo "$(RED)Error: HOST parameter required$(RESET)"
	@echo "Usage: make bootstrap-root-ssh HOST=listmonk"
	@exit 1
endif
	@chmod +x scripts/bootstrap-root-ssh.sh
	@BOOTSTRAP_USER="$(BOOTSTRAP_USER)" TARGET_USER="$(TARGET_USER)" \
		scripts/bootstrap-root-ssh.sh "$(HOST)"

bootstrap-root-ssh-services: ## Bootstrap root SSH via ladmin (caddy, listmonk, vikanjans)
	@chmod +x scripts/bootstrap-root-ssh.sh
	@failed=0; ok=0; \
	for host in caddy listmonk vikanjans; do \
		echo ""; echo "$(BLUE)==> $$host$(RESET)"; \
		if BOOTSTRAP_USER="$(BOOTSTRAP_USER)" scripts/bootstrap-root-ssh.sh "$$host"; then \
			ok=$$((ok+1)); \
		else \
			failed=$$((failed+1)); \
		fi; \
	done; \
	echo ""; echo "$(GREEN)Done: $$ok succeeded$(RESET), $(RED)$$failed failed$(RESET)"; \
	[ $$failed -eq 0 ]

mailcow-mailbox: ## Create Mailcow mailbox (usage: make mailcow-mailbox MAILBOX=alerts)
ifndef MAILBOX
	@echo "$(RED)Error: MAILBOX required$(RESET)"
	@echo "Usage: make mailcow-mailbox MAILBOX=alerts"
	@echo "Define mailboxes in inventories/production/group_vars/all/mailcow.yml"
	@exit 1
endif
	@chmod +x scripts/run-mailcow-mailbox.sh
	@MAILBOX="$(MAILBOX)" ./scripts/run-mailcow-mailbox.sh

mailcow-create-alerts: ## Alias for make mailcow-mailbox MAILBOX=alerts
	@$(MAKE) --no-print-directory mailcow-mailbox MAILBOX=alerts

vault-pull-infra-secrets: ## Pull Umami/Mattermost from hosts → .env → vault (not vault→.env)
	@chmod +x scripts/vault-pull-infra-secrets.sh scripts/vault-import-env.sh
	@./scripts/vault-pull-infra-secrets.sh

vault-export-env: ## Write vault secrets into .env (keeps existing non-empty keys)
	@chmod +x scripts/vault-export-env.sh
	@./scripts/vault-export-env.sh "$(or $(ENV_FILE),.env)"

vault-import-env: ## Merge .env secrets into Ansible vault (usage: make vault-import-env [ENV_FILE=.env])
	@chmod +x scripts/vault-import-env.sh
	@ENV_FILE="$(or $(ENV_FILE),.env)" scripts/vault-import-env.sh "$(or $(ENV_FILE),.env)"

bootstrap-root-ssh-failed: ## Bootstrap root SSH on hosts that failed direct root copy-ssh-keys
	@chmod +x scripts/bootstrap-root-ssh.sh
	@failed=0; ok=0; \
	for host in caddy listmonk vikanjans n8n qBittorrent actual caseware auto mailcow; do \
		echo ""; echo "$(BLUE)==> $$host$(RESET)"; \
		if BOOTSTRAP_USER="$(BOOTSTRAP_USER)" scripts/bootstrap-root-ssh.sh "$$host"; then \
			ok=$$((ok+1)); \
		else \
			failed=$$((failed+1)); \
		fi; \
	done; \
	echo ""; echo "$(GREEN)Done: $$ok succeeded$(RESET), $(RED)$$failed failed$(RESET)"; \
	[ $$failed -eq 0 ]

create-vault: require-ansible ## Create encrypted vault file for secrets (passwords, auth keys, etc.)
	@echo "$(YELLOW)Creating vault file for storing secrets...$(RESET)"
	$(ANSIBLE_VAULT) create $(INVENTORY)/group_vars/all/vault.yml
	@echo "$(GREEN)✓ Vault file created. Add your secrets here (e.g. vault_tailscale_auth_key)$(RESET)"

create-vm: ## Create Ansible controller VM on Proxmox
	@echo "$(YELLOW)Creating Ansible controller VM on Proxmox...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_PROXMOX) --ask-vault-pass
	@echo "$(GREEN)✓ VM creation complete$(RESET)"

monitoring: ## Install monitoring tools on all machines
	@echo "$(YELLOW)Installing monitoring tools...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --tags monitoring
	@echo "$(GREEN)✓ Monitoring installation complete$(RESET)"

proxmox-info: ## Show Proxmox VM/LXC info (usage: make proxmox-info [PROJECT=projectA] [ALL=true] [TYPE=lxc|qemu|all])
	@echo "$(YELLOW)Querying Proxmox guest info...$(RESET)"
	@EXTRA=""; \
	if [ -n "$(PROJECT)" ]; then EXTRA="$$EXTRA -e app_project=$(PROJECT)"; fi; \
	if [ "$(ALL)" = "true" ]; then EXTRA="$$EXTRA -e proxmox_info_all=true"; fi; \
	if [ -n "$(TYPE)" ]; then EXTRA="$$EXTRA -e proxmox_info_type=$(TYPE)"; fi; \
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_PROXMOX_INFO) $$EXTRA

app-provision: ## Provision app project containers/VMs on Proxmox (usage: make app-provision PROJECT=projectA)
ifndef PROJECT
	@echo "$(RED)Error: PROJECT parameter required$(RESET)"
	@echo "Usage: make app-provision PROJECT=projectA"
	@exit 1
endif
	@echo "$(YELLOW)Provisioning app project guests on Proxmox: $(PROJECT)$(RESET)"
	$(ANSIBLE_PLAYBOOK) playbooks/app/provision_vms.yml -e app_project=$(PROJECT)

app-configure: ## Configure OS + app on project guests (usage: make app-configure PROJECT=projectA)
ifndef PROJECT
	@echo "$(RED)Error: PROJECT parameter required$(RESET)"
	@echo "Usage: make app-configure PROJECT=projectA"
	@exit 1
endif
	@echo "$(YELLOW)Configuring app project guests: $(PROJECT)$(RESET)"
	$(ANSIBLE_PLAYBOOK) playbooks/app/configure_app.yml -e app_project=$(PROJECT)

app: ## Provision + configure app project (usage: make app PROJECT=projectA)
ifndef PROJECT
	@echo "$(RED)Error: PROJECT parameter required$(RESET)"
	@echo "Usage: make app PROJECT=projectA"
	@exit 1
endif
	@echo "$(YELLOW)Provisioning + configuring app project: $(PROJECT)$(RESET)"
	$(ANSIBLE_PLAYBOOK) playbooks/app/site.yml -e app_project=$(PROJECT)

# Timeshift snapshot and rollback
timeshift-snapshot: ## Create Timeshift snapshot (usage: make timeshift-snapshot HOST=dev02)
ifndef HOST
	@echo "$(RED)Error: HOST parameter required$(RESET)"
	@echo "Usage: make timeshift-snapshot HOST=dev02"
	@exit 1
endif
	@echo "$(YELLOW)Creating Timeshift snapshot on $(HOST)...$(RESET)"
	$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --limit $(HOST) --tags timeshift,snapshot
	@echo "$(GREEN)✓ Snapshot created$(RESET)"

timeshift-list: ## List Timeshift snapshots (usage: make timeshift-list HOST=dev02)
ifndef HOST
	@echo "$(RED)Error: HOST parameter required$(RESET)"
	@echo "Usage: make timeshift-list HOST=dev02"
	@exit 1
endif
	@echo "$(YELLOW)Listing Timeshift snapshots on $(HOST)...$(RESET)"
	@$(ANSIBLE_PLAYBOOK) playbooks/timeshift.yml --limit $(HOST) -e "timeshift_action=list" $(ANSIBLE_ARGS)

timeshift-restore: ## Restore from Timeshift snapshot (usage: make timeshift-restore HOST=dev02 SNAPSHOT=2025-12-17_21-30-00)
ifndef HOST
	@echo "$(RED)Error: HOST parameter required$(RESET)"
	@echo "Usage: make timeshift-restore HOST=dev02 SNAPSHOT=2025-12-17_21-30-00"
	@exit 1
endif
ifndef SNAPSHOT
	@echo "$(RED)Error: SNAPSHOT parameter required$(RESET)"
	@echo "Usage: make timeshift-restore HOST=dev02 SNAPSHOT=2025-12-17_21-30-00"
	@echo "$(YELLOW)Available snapshots:$(RESET)"
	@$(MAKE) timeshift-list HOST=$(HOST)
	@exit 1
endif
	@echo "$(RED)WARNING: This will restore the system to snapshot $(SNAPSHOT)$(RESET)"
	@echo "$(YELLOW)This action cannot be undone. Continue? [y/N]$(RESET)"
	@read -r confirm && [ "$$confirm" = "y" ] || exit 1
	@echo "$(YELLOW)Restoring snapshot $(SNAPSHOT) on $(HOST)...$(RESET)"
	@$(ANSIBLE_PLAYBOOK) playbooks/timeshift.yml --limit $(HOST) -e "timeshift_action=restore timeshift_snapshot=$(SNAPSHOT)" $(ANSIBLE_ARGS)
	@echo "$(GREEN)✓ Snapshot restored$(RESET)"

test-connectivity: ## Test host connectivity with detailed diagnostics and recommendations
	@echo "$(YELLOW)Testing host connectivity...$(RESET)"
	@if [ -f "test_connectivity.py" ]; then \
		python3 test_connectivity.py --hosts-file $(INVENTORY_HOSTS); \
	else \
		echo "$(RED)Error: test_connectivity.py not found$(RESET)"; \
		exit 1; \
	fi

auto-fallback: ## Automatically switch to fallback IPs when primary IPs fail
	@echo "$(YELLOW)Auto-fallback: Testing and switching to working IPs...$(RESET)"
	@if [ -f "auto-fallback.sh" ]; then \
		chmod +x auto-fallback.sh && ./auto-fallback.sh; \
	else \
		echo "$(RED)Error: auto-fallback.sh not found$(RESET)"; \
		exit 1; \
	fi
