package handlers

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/Wikid82/charon/backend/internal/models"
	"github.com/Wikid82/charon/backend/internal/services"
	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// setupSingleSourceTestDB creates a test database for single-source provider-event tests.
func setupSingleSourceTestDB(t *testing.T) *gorm.DB {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	err = db.AutoMigrate(
		&models.NotificationProvider{},
		&models.NotificationConfig{},
		&models.Setting{},
	)
	require.NoError(t, err)

	// Enable feature flag for provider-event model
	setting := &models.Setting{
		Key:      "feature.notifications.security_provider_events.enabled",
		Value:    "true",
		Type:     "bool",
		Category: "feature",
	}
	require.NoError(t, db.Create(setting).Error)

	return db
}

// TestR2_ProviderSecurityEventsCrowdSecDecisions tests that provider security controls include CrowdSec decisions (R2).
func TestR2_ProviderSecurityEventsCrowdSecDecisions(t *testing.T) {
	db := setupSingleSourceTestDB(t)

	// Create provider with CrowdSec decisions enabled
	provider := &models.NotificationProvider{
		Name:                            "Test Provider with CrowdSec",
		Type:                            "webhook",
		URL:                             "https://example.com/webhook",
		Enabled:                         true,
		NotifySecurityWAFBlocks:         true,
		NotifySecurityACLDenies:         false,
		NotifySecurityRateLimitHits:     true,
		NotifySecurityCrowdSecDecisions: true,
	}
	require.NoError(t, db.Create(provider).Error)

	service := services.NewEnhancedSecurityNotificationService(db)
	handler := NewSecurityNotificationHandler(service)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/api/v1/notifications/settings/security", http.NoBody)

	handler.GetSettings(c)

	assert.Equal(t, http.StatusOK, w.Code)

	var response models.NotificationConfig
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	// Verify CrowdSec decisions field is aggregated
	assert.True(t, response.NotifyCrowdSecDecisions, "security_crowdsec_enabled should be true")
	assert.True(t, response.NotifyWAFBlocks, "security_waf_enabled should be true")
	assert.False(t, response.NotifyACLDenies, "security_acl_enabled should be false")
	assert.True(t, response.NotifyRateLimitHits, "security_rate_limit_enabled should be true")
}

// TestR2_ProviderSecurityEventsCrowdSecDecisionsORSemantics tests OR aggregation for CrowdSec decisions.
func TestR2_ProviderSecurityEventsCrowdSecDecisionsORSemantics(t *testing.T) {
	db := setupSingleSourceTestDB(t)

	// Create two providers: one with CrowdSec enabled, one without
	provider1 := &models.NotificationProvider{
		Name:                            "Provider 1",
		Type:                            "webhook",
		Enabled:                         true,
		NotifySecurityCrowdSecDecisions: false,
	}
	provider2 := &models.NotificationProvider{
		Name:                            "Provider 2",
		Type:                            "discord",
		Enabled:                         true,
		NotifySecurityCrowdSecDecisions: true,
	}
	require.NoError(t, db.Create(provider1).Error)
	require.NoError(t, db.Create(provider2).Error)

	service := services.NewEnhancedSecurityNotificationService(db)
	handler := NewSecurityNotificationHandler(service)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/api/v1/notifications/settings/security", http.NoBody)

	handler.GetSettings(c)

	assert.Equal(t, http.StatusOK, w.Code)

	var response models.NotificationConfig
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	// OR semantics: if ANY provider has true, result is true
	assert.True(t, response.NotifyCrowdSecDecisions, "CrowdSec should be enabled (OR semantics)")
}

// TestR6_LegacySecuritySettingsWrite410Gone tests that legacy write endpoints return 410 Gone (R6).
func TestR6_LegacySecuritySettingsWrite410Gone(t *testing.T) {
	db := setupSingleSourceTestDB(t)

	service := services.NewEnhancedSecurityNotificationService(db)
	handler := NewSecurityNotificationHandler(service)


	// Test canonical endpoint: PUT /api/v1/notifications/settings/security
	t.Run("CanonicalEndpoint", func(t *testing.T) {
		reqBody := map[string]interface{}{
			"security_waf_enabled":        true,
			"security_acl_enabled":        false,
			"security_rate_limit_enabled": true,
			"security_crowdsec_enabled":   true,
		}
		bodyBytes, _ := json.Marshal(reqBody)

		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest("PUT", "/api/v1/notifications/settings/security", bytes.NewReader(bodyBytes))
		c.Request.Header.Set("Content-Type", "application/json")
		// Simulate admin context
		c.Set("role", "admin")

		handler.UpdateSettings(c)

		assert.Equal(t, http.StatusGone, w.Code, "Canonical endpoint should return 410 Gone")

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		// Verify 410 Gone contract fields
		assert.Equal(t, "legacy_security_settings_deprecated", response["error"])
		assert.Equal(t, "Use provider Notification Events.", response["message"])
		assert.Equal(t, "LEGACY_SECURITY_SETTINGS_DEPRECATED", response["code"])
	})

	// Test deprecated endpoint: PUT /api/v1/security/notifications/settings
	t.Run("DeprecatedEndpoint", func(t *testing.T) {
		reqBody := map[string]interface{}{
			"security_waf_enabled": true,
		}
		bodyBytes, _ := json.Marshal(reqBody)

		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest("PUT", "/api/v1/security/notifications/settings", bytes.NewReader(bodyBytes))
		c.Request.Header.Set("Content-Type", "application/json")
		// Simulate admin context
		c.Set("role", "admin")

		handler.DeprecatedUpdateSettings(c)

		assert.Equal(t, http.StatusGone, w.Code, "Deprecated endpoint should return 410 Gone")

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		// Verify 410 Gone contract fields
		assert.Equal(t, "legacy_security_settings_deprecated", response["error"])
		assert.Equal(t, "Use provider Notification Events.", response["message"])
		assert.Equal(t, "LEGACY_SECURITY_SETTINGS_DEPRECATED", response["code"])
	})
}

// TestR6_LegacyWrite410GoneNoMutation tests that 410 Gone does not mutate provider state.
func TestR6_LegacyWrite410GoneNoMutation(t *testing.T) {
	db := setupSingleSourceTestDB(t)

	// Create a provider with specific state
	provider := &models.NotificationProvider{
		Name:                            "Test Provider",
		Type:                            "webhook",
		Enabled:                         true,
		NotifySecurityWAFBlocks:         false,
		NotifySecurityCrowdSecDecisions: false,
	}
	require.NoError(t, db.Create(provider).Error)

	service := services.NewEnhancedSecurityNotificationService(db)
	handler := NewSecurityNotificationHandler(service)


	// Attempt PUT to canonical endpoint
	reqBody := map[string]interface{}{
		"security_waf_enabled":      true,
		"security_crowdsec_enabled": true,
	}
	bodyBytes, _ := json.Marshal(reqBody)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("PUT", "/api/v1/notifications/settings/security", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Set("role", "admin")

	handler.UpdateSettings(c)

	assert.Equal(t, http.StatusGone, w.Code)

	// Verify provider state was NOT mutated
	var fetchedProvider models.NotificationProvider
	err := db.First(&fetchedProvider, "id = ?", provider.ID).Error
	require.NoError(t, err)

	assert.False(t, fetchedProvider.NotifySecurityWAFBlocks, "WAF should remain false (no mutation)")
	assert.False(t, fetchedProvider.NotifySecurityCrowdSecDecisions, "CrowdSec should remain false (no mutation)")
}

// TestProviderCRUD_SecurityEventsIncludeCrowdSec tests provider create/update persists CrowdSec decisions.
func TestProviderCRUD_SecurityEventsIncludeCrowdSec(t *testing.T) {
	db := setupSingleSourceTestDB(t)

	service := services.NewNotificationService(db, nil)
	handler := NewNotificationProviderHandler(service)


	// Test CREATE
	t.Run("CreatePersistsCrowdSec", func(t *testing.T) {
		reqBody := notificationProviderUpsertRequest{
			Name:                            "CrowdSec Provider",
			Type:                            "discord",
			URL:                             "https://discord.com/api/webhooks/123/abc",
			Enabled:                         true,
			NotifySecurityWAFBlocks:         true,
			NotifySecurityCrowdSecDecisions: true,
		}
		bodyBytes, _ := json.Marshal(reqBody)

		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest("POST", "/api/v1/notification-providers", bytes.NewReader(bodyBytes))
		c.Request.Header.Set("Content-Type", "application/json")
		c.Set("role", "admin")

		handler.Create(c)

		assert.Equal(t, http.StatusCreated, w.Code)

		var response models.NotificationProvider
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		assert.True(t, response.NotifySecurityWAFBlocks, "WAF should be persisted")
		assert.True(t, response.NotifySecurityCrowdSecDecisions, "CrowdSec should be persisted")
	})

	// Test UPDATE
	t.Run("UpdatePersistsCrowdSec", func(t *testing.T) {
		// Create initial provider
		provider := &models.NotificationProvider{
			Name:                            "Initial Provider",
			Type:                            "discord",
			URL:                             "https://discord.com/api/webhooks/123/abc",
			Enabled:                         true,
			NotifySecurityCrowdSecDecisions: false,
		}
		require.NoError(t, db.Create(provider).Error)

		// Update to enable CrowdSec
		reqBody := notificationProviderUpsertRequest{
			Name:                            "Updated Provider",
			Type:                            "discord",
			URL:                             "https://discord.com/api/webhooks/123/abc",
			Enabled:                         true,
			NotifySecurityCrowdSecDecisions: true,
		}
		bodyBytes, _ := json.Marshal(reqBody)

		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest("PUT", "/api/v1/notification-providers/"+provider.ID, bytes.NewReader(bodyBytes))
		c.Request.Header.Set("Content-Type", "application/json")
		c.Params = gin.Params{{Key: "id", Value: provider.ID}}
		c.Set("role", "admin")

		handler.Update(c)

		assert.Equal(t, http.StatusOK, w.Code)

		var response models.NotificationProvider
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		assert.True(t, response.NotifySecurityCrowdSecDecisions, "CrowdSec should be updated to true")
	})
}

// TestR2_CompatibilityGETIncludesCrowdSec tests that compatibility response includes security_crowdsec_enabled.
func TestR2_CompatibilityGETIncludesCrowdSec(t *testing.T) {
	db := setupSingleSourceTestDB(t)

	provider := &models.NotificationProvider{
		Name:                            "Test Provider",
		Type:                            "webhook",
		Enabled:                         true,
		NotifySecurityCrowdSecDecisions: true,
	}
	require.NoError(t, db.Create(provider).Error)

	service := services.NewEnhancedSecurityNotificationService(db)
	handler := NewSecurityNotificationHandler(service)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/api/v1/notifications/settings/security", http.NoBody)

	handler.GetSettings(c)

	assert.Equal(t, http.StatusOK, w.Code)

	// Verify JSON response includes correct field name
	var rawResponse map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &rawResponse)
	require.NoError(t, err)

	assert.Equal(t, true, rawResponse["security_crowdsec_enabled"], "API should return security_crowdsec_enabled field")

	// Verify notify_* field is NOT present (API contract check)
	_, hasNotifyCrowdSec := rawResponse["notify_crowdsec_decisions"]
	assert.False(t, hasNotifyCrowdSec, "API should NOT expose notify_crowdsec_decisions (use security_crowdsec_enabled)")
}
