package main

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"os"
	"os/exec"
	"time"

	"github.com/dtoro/oikos/internal/secrets"
	"github.com/dtoro/oikos/internal/safego"
)

func main() {
	ctx := context.Background()

	port := os.Getenv("WEBHOOK_LISTEN")
	if port == "" {
		port = ":9797"
	}

	repoDir := os.Getenv("WEBHOOK_REPO_DIR")
	if repoDir == "" {
		repoDir = os.Getenv("HOME") + "/Projects/oikos"
	}

	// Create secrets manager once, share between HMAC resolution and deploy
	sec := newSecrets()
	secret := resolveWebhookHMAC(ctx, sec)
	if secret == "" {
		fmt.Fprintln(os.Stderr, "WEBHOOK_HMAC_SECRET must be set (env var or Infisical webhook_hmac-secret)")
		os.Exit(1)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("/deploy", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "method not allowed", 405)
			return
		}

		body, err := io.ReadAll(r.Body)
		if err != nil {
			http.Error(w, "read body failed", 400)
			return
		}

		sigHex := r.Header.Get("X-Hub-Signature-256")
		if sigHex == "" {
			http.Error(w, "missing signature", 401)
			return
		}

		mac := hmac.New(sha256.New, []byte(secret))
		mac.Write(body)
		expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))

		if !hmac.Equal([]byte(sigHex), []byte(expected)) {
			slog.Warn("webhook: invalid signature")
			http.Error(w, "invalid signature", 401)
			return
		}

		slog.Info("webhook: deploy triggered")
		w.WriteHeader(http.StatusAccepted)
		w.Write([]byte(`{"status":"deploy started"}`))

		safego.Go("webhook:deploy", func() {
			apiToken := ""
			if sec != nil {
				apiToken = secrets.ResolveSecret(ctx, sec, "api_token", "")
			}
			cmd := exec.Command(repoDir + "/scripts/deploy.sh")
			cmd.Dir = repoDir
			cmd.Env = append(os.Environ(),
				"REPO_DIR="+repoDir,
				"PROFILE=full",
				"OIKOS_API_TOKEN="+apiToken,
			)
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr
			start := time.Now()
			if err := cmd.Run(); err != nil {
				slog.Error("webhook: deploy failed", "error", err, "duration", time.Since(start))
				return
			}
			slog.Info("webhook: deploy succeeded", "duration", time.Since(start))
		})
	})

	mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(200)
		w.Write([]byte("ok"))
	})

	slog.Info("webhook: listening", "port", port)
	if err := http.ListenAndServe(port, mux); err != nil {
		slog.Error("webhook: serve failed", "error", err)
		os.Exit(1)
	}
}

// newSecrets creates the Infisical secrets manager from env vars.
func newSecrets() *secrets.Manager {
	return secrets.NewManagerFromConfig(
		os.Getenv("OIKOS_INFISICAL_SITE_URL"),
		os.Getenv("OIKOS_INFISICAL_CLIENT_ID"),
		os.Getenv("OIKOS_INFISICAL_CLIENT_SECRET"),
		os.Getenv("OIKOS_INFISICAL_PROJECT_ID"),
		os.Getenv("OIKOS_INFISICAL_ENV"),
		os.Getenv("OIKOS_SECRETS_DIR"),
	)
}

// resolveWebhookHMAC fetches the webhook HMAC secret from Infisical,
// falling back to the WEBHOOK_HMAC_SECRET env var.
func resolveWebhookHMAC(ctx context.Context, sec *secrets.Manager) string {
	envFallback := os.Getenv("WEBHOOK_HMAC_SECRET")
	if sec == nil {
		return envFallback
	}
	return secrets.ResolveSecret(ctx, sec, "webhook_hmac-secret", envFallback)
}