package handlers

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/service"
)

func TestVerifySignature_ValidSignature(t *testing.T) {
	h := &WoodpeckerWebhookHandler{webhookSecret: "test-secret"}
	body := []byte(`{"event":"push","repo":{"name":"test"},"build":{"status":"success"}}`)

	// Generate a valid signature
	mac := hmac.New(sha256.New, []byte("test-secret"))
	mac.Write(body)
	signature := "sha256=" + hex.EncodeToString(mac.Sum(nil))

	if !h.verifySignature(body, signature) {
		t.Error("expected valid signature to pass verification")
	}
}

func TestVerifySignature_InvalidSignature(t *testing.T) {
	h := &WoodpeckerWebhookHandler{webhookSecret: "test-secret"}
	body := []byte(`{"event":"push","repo":{"name":"test"},"build":{"status":"success"}}`)

	if h.verifySignature(body, "sha256=invalid") {
		t.Error("expected invalid signature to fail verification")
	}
}

func TestVerifySignature_EmptySignature(t *testing.T) {
	h := &WoodpeckerWebhookHandler{webhookSecret: "test-secret"}
	body := []byte(`{"event":"push"}`)

	if h.verifySignature(body, "") {
		t.Error("expected empty signature to fail verification")
	}
}

func TestVerifySignature_WrongSecret(t *testing.T) {
	h := &WoodpeckerWebhookHandler{webhookSecret: "test-secret"}
	body := []byte(`{"event":"push"}`)

	// Generate signature with different secret
	mac := hmac.New(sha256.New, []byte("wrong-secret"))
	mac.Write(body)
	signature := "sha256=" + hex.EncodeToString(mac.Sum(nil))

	if h.verifySignature(body, signature) {
		t.Error("expected signature with wrong secret to fail verification")
	}
}

func TestVerifySignature_WithoutPrefix(t *testing.T) {
	h := &WoodpeckerWebhookHandler{webhookSecret: "test-secret"}
	body := []byte(`{"event":"push"}`)

	// Generate valid signature without sha256= prefix
	mac := hmac.New(sha256.New, []byte("test-secret"))
	mac.Write(body)
	signature := hex.EncodeToString(mac.Sum(nil))

	// Should still work - we strip the prefix
	if !h.verifySignature(body, signature) {
		t.Error("expected signature without prefix to pass verification")
	}
}

func TestVerifySignature_TamperedBody(t *testing.T) {
	h := &WoodpeckerWebhookHandler{webhookSecret: "test-secret"}
	originalBody := []byte(`{"event":"push","repo":{"name":"test"}}`)
	tamperedBody := []byte(`{"event":"push","repo":{"name":"hacked"}}`)

	// Generate signature for original body
	mac := hmac.New(sha256.New, []byte("test-secret"))
	mac.Write(originalBody)
	signature := "sha256=" + hex.EncodeToString(mac.Sum(nil))

	// Verify against tampered body should fail
	if h.verifySignature(tamperedBody, signature) {
		t.Error("expected tampered body to fail verification")
	}
}

func TestWoodpeckerWebhookHandler_SetOperationService(t *testing.T) {
	h := &WoodpeckerWebhookHandler{logger: slog.Default()}

	t.Run("sets non-nil service", func(t *testing.T) {
		repo := newMockOperationRepo()
		opSvc := service.NewOperationService(repo)
		result := h.SetOperationService(opSvc)
		if h.operationService != opSvc {
			t.Error("expected operation service to be set")
		}
		if result != h {
			t.Error("expected fluent return of handler")
		}
	})

	t.Run("ignores nil service", func(t *testing.T) {
		repo := newMockOperationRepo()
		opSvc := service.NewOperationService(repo)
		h.SetOperationService(opSvc)
		h.SetOperationService(nil)
		if h.operationService != opSvc {
			t.Error("nil should not clear operation service")
		}
	})
}

func TestWoodpeckerWebhookHandler_TracksOperation(t *testing.T) {
	opRepo := newMockOperationRepo()
	opSvc := service.NewOperationService(opRepo)

	h := &WoodpeckerWebhookHandler{
		operationService: opSvc,
		logger:           slog.Default(),
		registryURL:      "registry.test:5000",
		defaultDomain:    "test.ai",
		clusterIP:        "1.2.3.4",
	}

	payload := WoodpeckerPayload{
		Event: "push",
		Repo: WoodpeckerRepo{
			Name:     "my-project",
			FullName: "org/my-project",
		},
		Build: WoodpeckerBuild{
			Number: 42,
			Status: "success",
			Branch: "main",
			Commit: "abc12345def67890",
		},
	}
	body, _ := json.Marshal(payload)

	req := httptest.NewRequest(http.MethodPost, "/webhooks/woodpecker", strings.NewReader(string(body)))
	req.Header.Set("Content-Type", "application/json")

	rec := httptest.NewRecorder()
	h.HandleWebhook(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d. Body: %s", rec.Code, rec.Body.String())
	}

	// Verify operation was created
	if opRepo.count() != 1 {
		t.Fatalf("expected 1 operation, got %d", opRepo.count())
	}

	// Verify operation details
	for _, op := range opRepo.operations {
		if op.Type != domain.OperationTypeBuild {
			t.Errorf("expected operation type build, got %s", op.Type)
		}
		if op.ProjectID != "my-project" {
			t.Errorf("expected project_id my-project, got %s", op.ProjectID)
		}
		if op.Status != domain.OperationStatusCompleted {
			t.Errorf("expected operation status completed, got %s", op.Status)
		}
		if op.ExternalRef != "build#42" {
			t.Errorf("expected external_ref build#42, got %s", op.ExternalRef)
		}
	}

	// Verify response contains operation_id
	var resp struct {
		Data map[string]any `json:"data"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}
	opID, ok := resp.Data["operation_id"]
	if !ok {
		t.Error("response missing operation_id field")
	}
	if opID == "" {
		t.Error("operation_id should not be empty")
	}
}

func TestWoodpeckerWebhookHandler_LinksToParentOperation(t *testing.T) {
	opRepo := newMockOperationRepo()
	opSvc := service.NewOperationService(opRepo)

	// Create a parent operation (component.add) that has the same commit SHA
	parentID, _ := opSvc.StartOperation(
		t.Context(),
		"my-project",
		domain.OperationTypeComponentAdd,
		map[string]any{"type": "service", "name": "auth-api"},
		"",
	)
	// Set the commit SHA on the parent (simulates component add creating a commit)
	opSvc.SetCommitSHA(t.Context(), parentID, "abc12345def67890")
	opSvc.CompleteOperation(t.Context(), parentID, nil)

	h := &WoodpeckerWebhookHandler{
		operationService: opSvc,
		logger:           slog.Default(),
		registryURL:      "registry.test:5000",
	}

	payload := WoodpeckerPayload{
		Event: "push",
		Repo: WoodpeckerRepo{
			Name:     "my-project",
			FullName: "org/my-project",
		},
		Build: WoodpeckerBuild{
			Number: 43,
			Status: "success",
			Branch: "main",
			Commit: "abc12345def67890",
		},
	}
	body, _ := json.Marshal(payload)

	req := httptest.NewRequest(http.MethodPost, "/webhooks/woodpecker", strings.NewReader(string(body)))
	rec := httptest.NewRecorder()
	h.HandleWebhook(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d. Body: %s", rec.Code, rec.Body.String())
	}

	// Should now have 2 operations: parent (component.add) and child (build)
	if opRepo.count() != 2 {
		t.Fatalf("expected 2 operations, got %d", opRepo.count())
	}

	// Find the build operation and verify it's linked to parent
	for _, op := range opRepo.operations {
		if op.Type == domain.OperationTypeBuild {
			if op.TriggeredBy != parentID {
				t.Errorf("expected triggered_by %s, got %s", parentID, op.TriggeredBy)
			}
			return
		}
	}
	t.Error("build operation not found")
}

func TestWoodpeckerWebhookHandler_RecordsFailedBuilds(t *testing.T) {
	opRepo := newMockOperationRepo()
	opSvc := service.NewOperationService(opRepo)

	h := &WoodpeckerWebhookHandler{
		operationService: opSvc,
		logger:           slog.Default(),
	}

	payload := WoodpeckerPayload{
		Event: "push",
		Repo:  WoodpeckerRepo{Name: "my-project", FullName: "org/my-project"},
		Build: WoodpeckerBuild{
			Number: 99,
			Status: "failure",
			Branch: "main",
			Commit: "abc123",
		},
	}
	body, _ := json.Marshal(payload)

	req := httptest.NewRequest(http.MethodPost, "/webhooks/woodpecker", strings.NewReader(string(body)))
	rec := httptest.NewRecorder()
	h.HandleWebhook(rec, req)

	// Failed builds are now recorded for visibility
	if opRepo.count() != 1 {
		t.Errorf("expected 1 operation for failed build, got %d", opRepo.count())
	}

	// Verify the operation was marked as failed
	for _, op := range opRepo.operations {
		if op.Type != domain.OperationTypeCIBuild {
			t.Errorf("expected operation type ci.build, got %s", op.Type)
		}
		if op.Status != domain.OperationStatusFailed {
			t.Errorf("expected operation status failed, got %s", op.Status)
		}
	}

	// Verify response indicates build was recorded
	var resp struct {
		Data map[string]any `json:"data"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}
	if resp.Data["status"] != "recorded" {
		t.Errorf("expected status 'recorded', got %v", resp.Data["status"])
	}
}

func TestWoodpeckerWebhookHandler_IgnoresPendingBuilds(t *testing.T) {
	opRepo := newMockOperationRepo()
	opSvc := service.NewOperationService(opRepo)

	h := &WoodpeckerWebhookHandler{
		operationService: opSvc,
		logger:           slog.Default(),
	}

	payload := WoodpeckerPayload{
		Event: "push",
		Repo:  WoodpeckerRepo{Name: "my-project"},
		Build: WoodpeckerBuild{
			Status: "pending",
			Branch: "main",
			Commit: "abc123",
		},
	}
	body, _ := json.Marshal(payload)

	req := httptest.NewRequest(http.MethodPost, "/webhooks/woodpecker", strings.NewReader(string(body)))
	rec := httptest.NewRecorder()
	h.HandleWebhook(rec, req)

	// Pending/running builds are ignored (only success and failure are handled)
	if opRepo.count() != 0 {
		t.Errorf("expected no operations for pending build, got %d", opRepo.count())
	}
}
