package service

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestShouldSendWebhookByMinSeverity(t *testing.T) {
	tests := []struct {
		name         string
		minSeverity  string
		ruleSeverity string
		want         bool
	}{
		{"critical >= critical", "critical", "critical", true},
		{"warning >= critical", "critical", "warning", false},
		{"critical >= warning", "warning", "critical", true},
		{"warning >= warning", "warning", "warning", true},
		{"info >= warning", "warning", "info", false},
		{"info >= info", "info", "info", true},
		{"empty min sends all", "", "info", true},
		{"unknown severity sends by default", "unknown", "info", true},
		{"case insensitive", "CRITICAL", "Warning", false},
		{"case insensitive 2", "critical", "WARNING", false},
		{"spaces trimmed", " critical ", " warning ", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := shouldSendWebhookByMinSeverity(tt.minSeverity, tt.ruleSeverity)
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestWebhookService_BuildAlertPayload(t *testing.T) {
	svc := &WebhookService{}

	rule := &OpsAlertRule{
		ID:       1,
		Name:     "Test Rule",
		Severity: "critical",
	}

	event := &OpsAlertEvent{
		ID:          1,
		RuleID:      1,
		Title:       "Test Alert",
		Description: "Test alert description",
		Severity:    "critical",
		Status:      "firing",
	}

	t.Run("alert payload", func(t *testing.T) {
		payload := svc.buildAlertPayload(rule, event, false)
		assert.Equal(t, "alert", payload.Type)
		assert.NotEmpty(t, payload.Timestamp)
		assert.Equal(t, rule, payload.Data.Rule)
		assert.Equal(t, event, payload.Data.Event)
		assert.Empty(t, payload.Data.ResolvedAt)
	})

	t.Run("resolved payload", func(t *testing.T) {
		payload := svc.buildAlertPayload(rule, event, true)
		assert.Equal(t, "alert_resolved", payload.Type)
		assert.NotEmpty(t, payload.Data.ResolvedAt)
	})
}

func TestWebhookService_SignPayload(t *testing.T) {
	svc := &WebhookService{}

	t.Run("empty secret does not sign", func(t *testing.T) {
		payload := &OpsWebhookPayload{
			Type:      "alert",
			Timestamp: "2024-01-01T00:00:00Z",
		}
		err := svc.signPayload(payload, "")
		assert.NoError(t, err)
		assert.Empty(t, payload.Signature)
	})

	t.Run("nil payload returns nil", func(t *testing.T) {
		err := svc.signPayload(nil, "secret")
		assert.NoError(t, err)
	})

	t.Run("signs payload with HMAC-SHA256", func(t *testing.T) {
		payload := &OpsWebhookPayload{
			Type:      "alert",
			Timestamp: "2024-01-01T00:00:00Z",
			Data: OpsWebhookData{
				Rule: &OpsAlertRule{ID: 1},
			},
		}
		secret := "test-secret"

		err := svc.signPayload(payload, secret)
		require.NoError(t, err)
		assert.NotEmpty(t, payload.Signature)
		assert.Len(t, payload.Signature, 64) // SHA256 hex encoding = 64 chars

		// Verify signature
		signPayload := &OpsWebhookPayload{
			Type:      payload.Type,
			Timestamp: payload.Timestamp,
			Data:      payload.Data,
		}
		data, _ := json.Marshal(signPayload)
		mac := hmac.New(sha256.New, []byte(secret))
		mac.Write(data)
		expectedSig := hex.EncodeToString(mac.Sum(nil))
		assert.Equal(t, expectedSig, payload.Signature)
	})
}

func TestWebhookService_SendWebhook(t *testing.T) {
	svc := &WebhookService{
		httpClient: &http.Client{Timeout: 30 * time.Second},
	}

	t.Run("successful webhook", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, http.MethodPost, r.Method)
			assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
			assert.Contains(t, r.Header.Get("User-Agent"), "Sub2API-Webhook")

			body, _ := io.ReadAll(r.Body)
			assert.Contains(t, string(body), "test")
			w.WriteHeader(http.StatusOK)
		}))
		defer server.Close()

		payload := []byte(`{"type":"alert","test":true}`)
		err := svc.sendWebhook(context.Background(), server.URL, payload, 5, "")
		assert.NoError(t, err)
	})

	t.Run("webhook with signature header", func(t *testing.T) {
		secret := "test-secret"
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			sigHeader := r.Header.Get("X-Webhook-Signature")
			assert.NotEmpty(t, sigHeader)
			assert.True(t, strings.HasPrefix(sigHeader, "sha256="))
			w.WriteHeader(http.StatusOK)
		}))
		defer server.Close()

		payload := []byte(`{"type":"alert"}`)
		err := svc.sendWebhook(context.Background(), server.URL, payload, 5, secret)
		assert.NoError(t, err)
	})

	t.Run("webhook returns error status", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte("internal error"))
		}))
		defer server.Close()

		payload := []byte(`{"type":"alert"}`)
		err := svc.sendWebhook(context.Background(), server.URL, payload, 5, "")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "500")
	})

	t.Run("webhook timeout", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			time.Sleep(2 * time.Second)
			w.WriteHeader(http.StatusOK)
		}))
		defer server.Close()

		payload := []byte(`{"type":"alert"}`)
		ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
		defer cancel()

		err := svc.sendWebhook(ctx, server.URL, payload, 1, "")
		assert.Error(t, err)
	})

	t.Run("invalid URL", func(t *testing.T) {
		payload := []byte(`{"type":"alert"}`)
		err := svc.sendWebhook(context.Background(), "://invalid-url", payload, 5, "")
		assert.Error(t, err)
	})

	t.Run("nil http client returns error", func(t *testing.T) {
		svc := &WebhookService{httpClient: nil}
		payload := []byte(`{"type":"alert"}`)
		err := svc.sendWebhook(context.Background(), "http://example.com", payload, 5, "")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "not initialized")
	})
}

func TestWebhookService_SendAlertWebhook_NilChecks(t *testing.T) {
	t.Run("nil service returns nil", func(t *testing.T) {
		var svc *WebhookService
		err := svc.SendAlertWebhook(context.Background(), &OpsAlertRule{}, &OpsAlertEvent{}, false)
		assert.NoError(t, err)
	})

	t.Run("nil ops service returns nil", func(t *testing.T) {
		svc := &WebhookService{opsService: nil}
		err := svc.SendAlertWebhook(context.Background(), &OpsAlertRule{}, &OpsAlertEvent{}, false)
		assert.NoError(t, err)
	})
}
