// Package handlers provides HTTP handlers for the rdev API.
package handlers

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"strings"

	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/logging"
	"github.com/orchard9/rdev/internal/metrics"
	"github.com/orchard9/rdev/internal/port"
	"github.com/orchard9/rdev/internal/service"
	"github.com/orchard9/rdev/pkg/api"
)

// WoodpeckerWebhookHandler handles webhooks from Woodpecker CI.
type WoodpeckerWebhookHandler struct {
	deployer         port.Deployer
	dns              port.DNSProvider
	operationService *service.OperationService
	logger           *slog.Logger

	// Config
	webhookSecret string
	defaultDomain string
	registryURL   string
	clusterIP     string
}

// WoodpeckerWebhookConfig configures the webhook handler.
type WoodpeckerWebhookConfig struct {
	WebhookSecret string // HMAC secret for verifying webhooks
	DefaultDomain string // e.g., "threesix.ai"
	RegistryURL   string // e.g., "zot.threesix.svc.cluster.local:5000"
	ClusterIP     string // e.g., "208.122.204.172"
	Logger        *slog.Logger
}

// NewWoodpeckerWebhookHandler creates a new Woodpecker webhook handler.
func NewWoodpeckerWebhookHandler(
	deployer port.Deployer,
	dns port.DNSProvider,
	cfg WoodpeckerWebhookConfig,
) *WoodpeckerWebhookHandler {
	logger := cfg.Logger
	if logger == nil {
		logger = slog.Default()
	}
	return &WoodpeckerWebhookHandler{
		deployer:      deployer,
		dns:           dns,
		logger:        logger,
		webhookSecret: cfg.WebhookSecret,
		defaultDomain: cfg.DefaultDomain,
		registryURL:   cfg.RegistryURL,
		clusterIP:     cfg.ClusterIP,
	}
}

// SetOperationService sets the operation tracking service.
func (h *WoodpeckerWebhookHandler) SetOperationService(svc *service.OperationService) *WoodpeckerWebhookHandler {
	if svc != nil {
		h.operationService = svc
	}
	return h
}

// Mount registers the webhook routes.
func (h *WoodpeckerWebhookHandler) Mount(r api.Router) {
	// Woodpecker webhook endpoint - no API key auth, uses HMAC signature
	r.Post("/webhooks/woodpecker", h.HandleWebhook)
}

// WoodpeckerPayload represents a Woodpecker webhook payload.
// See: https://woodpecker-ci.org/docs/usage/webhooks
type WoodpeckerPayload struct {
	Event    string             `json:"event"` // "push", "pull_request", "tag", "deployment"
	Repo     WoodpeckerRepo     `json:"repo"`
	Build    WoodpeckerBuild    `json:"build"`
	Pipeline WoodpeckerPipeline `json:"pipeline"`
}

// WoodpeckerRepo represents repository info in the webhook.
type WoodpeckerRepo struct {
	ID       int64  `json:"id"`
	Owner    string `json:"owner"`
	Name     string `json:"name"`
	FullName string `json:"full_name"` // owner/name
	CloneURL string `json:"clone_url"`
	HTMLURL  string `json:"html_url"`
}

// WoodpeckerBuild represents build info in the webhook.
type WoodpeckerBuild struct {
	ID       int64  `json:"id"`
	Number   int64  `json:"number"`
	Status   string `json:"status"` // "success", "failure", "pending", "running"
	Event    string `json:"event"`
	Branch   string `json:"branch"`
	Commit   string `json:"commit"`
	Message  string `json:"message"`
	Author   string `json:"author"`
	Started  int64  `json:"started"`
	Finished int64  `json:"finished"`
}

// WoodpeckerPipeline represents pipeline info in the webhook.
type WoodpeckerPipeline struct {
	ID       int64  `json:"id"`
	Number   int64  `json:"number"`
	Status   string `json:"status"`
	Event    string `json:"event"`
	Branch   string `json:"branch"`
	Commit   string `json:"commit"`
	Started  int64  `json:"started"`
	Finished int64  `json:"finished"`
}

// HandleWebhook processes incoming Woodpecker webhooks.
// POST /webhooks/woodpecker
func (h *WoodpeckerWebhookHandler) HandleWebhook(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite)
	defer cancel()

	log := logging.FromContext(ctx).WithHandler("HandleWebhook")

	// Read body
	body, err := io.ReadAll(r.Body)
	if err != nil {
		log.Error("failed to read webhook body", logging.FieldError, err.Error())
		api.WriteBadRequest(w, r, "failed to read request body")
		return
	}

	// Debug log the raw payload for troubleshooting
	if h.logger.Enabled(ctx, slog.LevelDebug) {
		log.Debug("webhook payload received", "body", string(body))
	}

	// Verify signature if secret is configured
	if h.webhookSecret != "" {
		signature := r.Header.Get("X-Woodpecker-Signature")
		if !h.verifySignature(body, signature) {
			log.Warn("webhook signature verification failed")
			api.WriteUnauthorized(w, r, "invalid signature")
			return
		}
	}

	// Parse payload
	var payload WoodpeckerPayload
	if err := json.Unmarshal(body, &payload); err != nil {
		log.Error("failed to parse webhook payload", logging.FieldError, err.Error())
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	log.Info("received woodpecker webhook",
		"event", payload.Event,
		"repo", payload.Repo.FullName,
		"build_status", payload.Build.Status,
		"build_number", payload.Build.Number,
	)

	// Track failed builds for visibility
	if payload.Build.Status == "failure" {
		h.handleFailedBuild(ctx, payload)
		api.WriteSuccess(w, r, map[string]any{
			"status":  "recorded",
			"reason":  "build failed",
			"project": payload.Repo.Name,
			"build":   payload.Build.Number,
		})
		return
	}

	// Only process successful builds on main/master branch
	if payload.Build.Status != "success" {
		api.WriteSuccess(w, r, map[string]string{
			"status": "ignored",
			"reason": "build not successful",
			"build":  payload.Build.Status,
		})
		return
	}

	if payload.Build.Branch != "main" && payload.Build.Branch != "master" {
		api.WriteSuccess(w, r, map[string]string{
			"status": "ignored",
			"reason": "not main/master branch",
			"branch": payload.Build.Branch,
		})
		return
	}

	// Extract project name from repo name
	projectName := payload.Repo.Name

	// Build image tag from commit SHA (short)
	commitShort := payload.Build.Commit
	if len(commitShort) > 8 {
		commitShort = commitShort[:8]
	}
	imageTag := fmt.Sprintf("%s/%s:%s", h.registryURL, projectName, commitShort)

	log.Info("triggering deployment",
		logging.FieldProjectName, projectName,
		"image", imageTag,
		"commit", payload.Build.Commit,
	)

	// Create DNS record if needed
	if h.dns != nil {
		_, err := h.dns.CreateRecord(ctx, domain.DNSRecord{
			Type:    "A",
			Name:    projectName,
			Content: h.clusterIP,
			TTL:     1,
			Proxied: false,
		})
		if err != nil {
			log.Warn("failed to create DNS record", logging.FieldError, err.Error(), logging.FieldProjectName, projectName)
			// Continue anyway - DNS might already exist
		}
	}

	// Track build operation
	var operationID string
	if h.operationService != nil {
		operationID, _ = h.operationService.StartOperation(ctx, projectName,
			domain.OperationTypeBuild,
			map[string]any{
				"repo":         payload.Repo.FullName,
				"branch":       payload.Build.Branch,
				"commit":       payload.Build.Commit,
				"build_number": payload.Build.Number,
			}, "")

		if operationID != "" {
			// Set external reference to build number
			if opErr := h.operationService.SetExternalRef(ctx, operationID, fmt.Sprintf("build#%d", payload.Build.Number)); opErr != nil {
				log.Error("failed to set external ref", logging.FieldError, opErr.Error(), logging.FieldOperation, operationID)
			}

			// Link to parent operation via commit SHA
			if parent, err := h.operationService.FindByCommit(ctx, projectName, payload.Build.Commit); err == nil && parent != nil {
				if opErr := h.operationService.LinkToParent(ctx, operationID, parent.ID); opErr != nil {
					log.Error("failed to link to parent operation", logging.FieldError, opErr.Error(), logging.FieldOperation, operationID)
				}
			}

			// Build webhook only fires for successful builds
			if opErr := h.operationService.CompleteOperation(ctx, operationID, map[string]any{
				"image":  imageTag,
				"commit": payload.Build.Commit,
			}); opErr != nil {
				log.Error("failed to record operation completion", logging.FieldError, opErr.Error(), logging.FieldOperation, operationID)
			}
		}
	}

	// Note: Project-level deployment is skipped for composable projects.
	// Component deployments are created by createInitialComponentDeployment
	// and updated by the CI pipeline's kubectl set image commands.
	log.Info("build succeeded, component deployments updated by CI",
		logging.FieldProjectName, projectName,
		"commit", payload.Build.Commit,
	)

	resp := map[string]any{
		"status":  "success",
		"project": projectName,
		"image":   imageTag,
		"commit":  payload.Build.Commit,
		"note":    "component deployments managed by CI pipeline",
	}
	if operationID != "" {
		resp["operation_id"] = operationID
	}
	api.WriteSuccess(w, r, resp)
}

// verifySignature verifies the HMAC-SHA256 signature of the webhook payload.
func (h *WoodpeckerWebhookHandler) verifySignature(body []byte, signature string) bool {
	if signature == "" {
		return false
	}

	// Woodpecker sends signature as "sha256=<hex>"
	signature = strings.TrimPrefix(signature, "sha256=")

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

	return hmac.Equal([]byte(signature), []byte(expected))
}

// handleFailedBuild records a failed CI build for visibility and debugging.
func (h *WoodpeckerWebhookHandler) handleFailedBuild(ctx context.Context, payload WoodpeckerPayload) {
	projectName := payload.Repo.Name
	log := logging.FromContext(ctx).WithHandler("handleFailedBuild")

	log.Warn("CI build failed",
		logging.FieldProjectName, projectName,
		"build_number", payload.Build.Number,
		"branch", payload.Build.Branch,
		"commit", payload.Build.Commit,
		"author", payload.Build.Author,
	)

	// Record metrics
	metrics.RecordCIBuild(projectName, "failure")

	// Check if this looks like a registry push failure
	// (We can't get detailed logs here, but we track the failure)
	if payload.Build.Branch == "main" || payload.Build.Branch == "master" {
		// Failed builds on main are likely image push failures
		metrics.RecordCIPushFailure(projectName)
	}

	// Track as operation if operation service is configured
	if h.operationService != nil {
		operationID, _ := h.operationService.StartOperation(ctx, projectName,
			domain.OperationTypeCIBuild,
			map[string]any{
				"repo":         payload.Repo.FullName,
				"branch":       payload.Build.Branch,
				"commit":       payload.Build.Commit,
				"build_number": payload.Build.Number,
				"author":       payload.Build.Author,
			}, "")

		if operationID != "" {
			// Set external reference to build number
			if opErr := h.operationService.SetExternalRef(ctx, operationID, fmt.Sprintf("build#%d", payload.Build.Number)); opErr != nil {
				log.Error("failed to set external ref", logging.FieldError, opErr.Error(), logging.FieldOperation, operationID)
			}

			// Link to parent operation via commit SHA
			if parent, err := h.operationService.FindByCommit(ctx, projectName, payload.Build.Commit); err == nil && parent != nil {
				if opErr := h.operationService.LinkToParent(ctx, operationID, parent.ID); opErr != nil {
					log.Error("failed to link to parent operation", logging.FieldError, opErr.Error(), logging.FieldOperation, operationID)
				}
			}

			// Mark as failed
			if opErr := h.operationService.FailOperation(ctx, operationID, "CI build failed", ""); opErr != nil {
				log.Error("failed to record operation failure", logging.FieldError, opErr.Error(), logging.FieldOperation, operationID)
			}
		}
	}
}
