SHELL := /bin/bash
PY ?= .venv/bin/python
SLUG := k8s-gitops
CLUSTER := lab-$(SLUG)
CTX := kind-$(CLUSTER)
NS := assistant
TAG ?= 0.2.0
IMAGE := lab-k8s-gitops/inference
HTTP_PORT := 8082
URL := http://assistant.127.0.0.1.nip.io:$(HTTP_PORT)
# .env n'est ni inclus ni exporté : une variable passée en ligne de commande l'emporte toujours.
PROJECT ?= $(shell sed -n 's/^GOOGLE_CLOUD_PROJECT=//p' .env 2>/dev/null)
REGION ?= europe-west1
ADMIN_CIDR ?= 203.0.113.10/32
INGRESS_NGINX := https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.15.1/deploy/static/provider/kind/deploy.yaml
METRICS_SERVER := https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.9.0/components.yaml
ARGOCD := https://raw.githubusercontent.com/argoproj/argo-cd/v3.5.5/manifests/install.yaml
ROLLOUTS := https://github.com/argoproj/argo-rollouts/releases/download/v1.10.1/install.yaml
KPS := oci://ghcr.io/prometheus-community/charts/kube-prometheus-stack
KPS_VERSION := 92.9.0
K := kubectl --context $(CTX)
H := helm --kube-context $(CTX)
GIT := git -C gitops-repo -c user.name=lab-gitops -c user.email=lab@example.invalid
.PHONY: help setup test run up cluster addons monitoring rollouts argocd gitserver image secret gitops-init gitops-push gitops-revert app e2e e2e-vertex load status argocd-ui grafana-ui down clean reset tf-validate tf-plan cloud-up cloud-down cloud-status
.DEFAULT_GOAL := help
help: ## Liste les commandes
	@grep -hE '^[a-z0-9-]+:.*## ' Makefile | awk -F':.*## ' '{printf "  make %-14s %s\n", $$1, $$2}'

setup: ## Crée .venv et installe les dépendances
	@test -d .venv || python3 -m venv .venv
	@.venv/bin/pip install -q -r requirements-dev.txt
	@test -f .env || cp .env.example .env
	@echo "Prêt. Renseignez .env puis : make test"

test: ## Tests sans clé ni réseau (API + rendu du chart kind et GKE)
	@$(PY) -m pytest -q

run: ## Lance l'API en local (http://localhost:8000), variables lues dans .env
	@$(PY) -m uvicorn app.main:app --port 8000 --env-file .env

# ---------- Cluster kind + plateforme
up: cluster addons monitoring rollouts argocd gitserver image secret gitops-init gitops-push app ## Tout : cluster, Prometheus, Argo CD, Argo Rollouts, Git, application
	@echo "Application déclarée. Suivi : make status   Interfaces : make argocd-ui / make grafana-ui"

cluster:
	@kind get clusters 2>/dev/null | grep -qx $(CLUSTER) || kind create cluster --name $(CLUSTER) --config k8s/kind-config.yaml --wait 120s

addons:
	@$(K) apply -f $(INGRESS_NGINX) >/dev/null
	@$(K) apply -f $(METRICS_SERVER) >/dev/null
	@$(K) -n kube-system patch deploy metrics-server --type=json \
	  -p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]' >/dev/null 2>&1 || true
	@$(K) -n ingress-nginx rollout status deploy/ingress-nginx-controller --timeout=180s >/dev/null

monitoring: ## kube-prometheus-stack (Prometheus, Grafana) + tableau de bord
	@$(H) upgrade --install kps $(KPS) --version $(KPS_VERSION) -n monitoring --create-namespace \
	  -f monitoring/kps-values.yaml --wait --timeout 6m >/dev/null
	@$(K) apply -f monitoring/dashboard-assistant.yaml >/dev/null
	@echo "kube-prometheus-stack $(KPS_VERSION) prêt"

rollouts:
	@$(K) create namespace argo-rollouts --dry-run=client -o yaml | $(K) apply -f - >/dev/null
	@$(K) apply -n argo-rollouts --server-side --force-conflicts -f $(ROLLOUTS) >/dev/null
	@$(K) -n argo-rollouts rollout status deploy/argo-rollouts --timeout=180s >/dev/null
	@echo "Argo Rollouts prêt"

argocd:
	@$(K) create namespace argocd --dry-run=client -o yaml | $(K) apply -f - >/dev/null
	@$(K) apply -n argocd --server-side --force-conflicts -f $(ARGOCD) >/dev/null
	@$(K) -n argocd rollout status deploy/argocd-repo-server --timeout=240s >/dev/null
	@$(K) -n argocd rollout status statefulset/argocd-application-controller --timeout=240s >/dev/null
	@$(K) -n argocd rollout status deploy/argocd-server --timeout=240s >/dev/null
	@echo "Argo CD prêt"

gitserver:
	@docker build -q -t lab-k8s-gitops/git-server:2.54 -f k8s/git-server.Dockerfile k8s >/dev/null
	@kind load docker-image lab-k8s-gitops/git-server:2.54 --name $(CLUSTER) >/dev/null
	@$(K) apply -f k8s/git-server.yaml >/dev/null
	@$(K) -n git rollout status deploy/git-server --timeout=120s >/dev/null

image: ## Construit l'image TAG et la charge dans kind
	@docker build -q -t $(IMAGE):$(TAG) . >/dev/null
	@kind load docker-image $(IMAGE):$(TAG) --name $(CLUSTER) >/dev/null
	@echo "Image $(IMAGE):$(TAG) chargée"

secret: ## Secret de la clé Gemini (hors Git). Sans clé valide : mode simulé (LLM_BACKEND=fake)
	@$(K) create namespace $(NS) --dry-run=client -o yaml | $(K) apply -f - >/dev/null
	@KEY="$${GEMINI_API_KEY:-$$(sed -n 's/^GEMINI_API_KEY=//p' .env 2>/dev/null)}"; \
	 if [ -z "$$KEY" ] || [ "$$KEY" = "remplacez-moi" ]; then echo "Pas de clé Gemini : le lab tourne en mode simulé (LLM_BACKEND=fake)."; \
	 else printf '%s' "$$KEY" | $(K) -n $(NS) create secret generic inference-gemini \
	  --from-file=GEMINI_API_KEY=/dev/stdin --dry-run=client -o yaml | $(K) apply -f - >/dev/null && echo "Secret inference-gemini à jour"; fi

# ---------- GitOps : gitops-repo/ est le dépôt de déploiement, créé à partir de gitops-source/ (versionné)
gitops-init: ## Crée le dépôt git gitops-repo/ à partir de gitops-source/ (sans effet s'il existe)
	@test -d gitops-repo/.git || { mkdir -p gitops-repo && cp -r gitops-source/. gitops-repo/ \
	  && git init -q -b main gitops-repo && $(GIT) add -A && $(GIT) commit -qm "Déploiement initial : assistant 0.2.0" \
	  && echo "Dépôt gitops-repo/ créé"; }

gitops-push: ## Pousse gitops-repo/ vers le serveur Git du cluster et demande une synchronisation
	@$(K) -n git port-forward svc/git-server 19418:9418 >/dev/null 2>&1 & PF=$$!; sleep 2; \
	  git -C gitops-repo push -q git://127.0.0.1:19418/assistant.git main; RC=$$?; kill $$PF; exit $$RC
	@$(K) -n argocd annotate application assistant argocd.argoproj.io/refresh=normal --overwrite >/dev/null 2>&1 || true
	@echo "Poussé : $$(git -C gitops-repo log --oneline -1)"

gitops-revert: ## Annule le dernier commit de gitops-repo/ (git revert) et le pousse
	@$(GIT) revert --no-edit HEAD >/dev/null && $(MAKE) -s gitops-push

app: ## Déclare l'application dans Argo CD
	@$(K) apply -f argocd/application.yaml >/dev/null
	@echo "Application assistant déclarée"

# ---------- Vérifications
e2e: ## Test réel sur le cluster : Argo CD synchronisé, Rollout sain, réponse via l'Ingress, métriques dans Prometheus
	@KCTX=$(CTX) NS=$(NS) URL=$(URL) $(PY) scripts/e2e.py

e2e-vertex: ## Test réel du mode GKE : Vertex AI par identifiants ADC (ce que fournit Workload Identity)
	@test -n "$(PROJECT)" || { echo "PROJECT (ou GOOGLE_CLOUD_PROJECT) requis"; exit 1; }
	@env -u GEMINI_API_KEY -u GOOGLE_API_KEY GOOGLE_GENAI_USE_VERTEXAI=true GOOGLE_CLOUD_PROJECT=$(PROJECT) \
	  GOOGLE_CLOUD_LOCATION=$(REGION) MODEL=gemini-2.5-flash $(PY) scripts/e2e_vertex.py

load: ## Charge continue (Ctrl-C pour arrêter) : CONC clients pendant DUR secondes
	@$(PY) scripts/loadtest.py --url $(URL) --concurrency $${CONC:-8} --duration $${DUR:-300} --context $(CTX)

status: ## Application Argo CD, Rollout, pods
	@$(K) -n argocd get application assistant -o custom-columns=APP:.metadata.name,SYNC:.status.sync.status,SANTE:.status.health.status,REVISION:.status.sync.revision 2>/dev/null || echo "Cluster absent."
	@$(K) -n $(NS) get rollout,pods 2>/dev/null || true

argocd-ui: ## Interface Argo CD sur https://localhost:8443 (admin / mot de passe affiché)
	@echo "admin / $$($(K) -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d)"
	@$(K) -n argocd port-forward svc/argocd-server 8443:443

grafana-ui: ## Grafana sur http://localhost:3000 (admin / lab-k8s-gitops)
	@$(K) -n monitoring port-forward svc/kps-grafana 3000:80

down: ## Supprime le cluster kind et vérifie
	@kind delete cluster --name $(CLUSTER)
	@! kind get clusters 2>/dev/null | grep -qx $(CLUSTER) && echo "Cluster $(CLUSTER) supprimé."

clean: ## Supprime le cluster, les caches, le plan Terraform et les images locales (garde gitops-repo/ et son historique)
	@kind get clusters 2>/dev/null | grep -qx $(CLUSTER) && $(MAKE) -s down || true
	@rm -rf .pytest_cache tests/__pycache__ app/__pycache__ scripts/__pycache__ infra/.terraform infra/tfplan
	@echo "gitops-repo/ conservé (votre historique de déploiement). Pour repartir de gitops-source/ : rm -rf gitops-repo && make gitops-init"
	@docker image rm -f $$( (docker images -q $(IMAGE); docker images -q lab-k8s-gitops/git-server) | sort -u) >/dev/null 2>&1 || true
	@echo "Artefacts supprimés."

reset: clean ## clean + supprime .venv (.env conservé)
	@rm -rf .venv
	@echo "Environnement supprimé (.env conservé)."

# ---------- GKE Autopilot (Terraform)
tf-validate: ## terraform init + validate (aucun appel à l'API GKE)
	@cd infra && terraform init -input=false >/dev/null && terraform fmt -check && terraform validate

tf-plan: tf-validate ## terraform plan : ce qui serait créé, sans rien créer
	@cd infra && terraform plan -input=false -var project_id=$(PROJECT) -var admin_cidr=$(ADMIN_CIDR) -out=tfplan

cloud-up: ## Crée le cluster GKE Autopilot (payant : voir la leçon 6). Exige CONFIRM=oui
	@test "$(CONFIRM)" = "oui" || { echo "Refusé : ajoutez CONFIRM=oui (coût horaire, voir la leçon 6)."; exit 1; }
	@cd infra && terraform apply -input=false -var project_id=$(PROJECT) -var admin_cidr=$(ADMIN_CIDR) -var deletion_protection=false

cloud-down: ## Détruit tout ce que Terraform a créé, puis vérifie qu'aucun cluster du lab ne subsiste
	@cd infra && { test -f terraform.tfstate && terraform destroy -input=false -auto-approve \
	  -var project_id=$(PROJECT) -var admin_cidr=$(ADMIN_CIDR) -var deletion_protection=false || echo "Aucun état Terraform : rien à détruire."; }
	@OUT=$$($(MAKE) -s cloud-status); echo "$$OUT"; echo "$$OUT" | grep -q "Aucune ressource" || { echo "Des ressources subsistent."; exit 1; }

cloud-status: ## Liste ce qui existe encore : cluster, dépôt d'images, réseau, compte de nœuds du lab
	@C=$$(gcloud --quiet container clusters list --project $(PROJECT) --filter="resourceLabels.formation-lab=k8s-gitops" --format="value(name)" 2>/dev/null); \
	 R=$$(gcloud --quiet artifacts repositories list --project $(PROJECT) --location $(REGION) --filter="name~lab-k8s-gitops" --format="value(name)" 2>/dev/null); \
	 N=$$(gcloud --quiet compute networks list --project $(PROJECT) --filter="name=lab-k8s-gitops" --format="value(name)" 2>/dev/null); \
	 A=$$(gcloud --quiet iam service-accounts list --project $(PROJECT) --filter="email~^lab-k8s-gitops-nodes@" --format="value(email)" 2>/dev/null); \
	 if [ -z "$$C$$R$$N$$A" ]; then echo "Aucune ressource formation-lab=k8s-gitops."; \
	 else echo "Clusters : $$C"; echo "Dépôts : $$R"; echo "Réseaux : $$N"; echo "Comptes : $$A"; fi
