package handlers

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"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"
)

// TestFinalBlocker1_DestinationAmbiguous_ZeroManagedProviders tests that destination_ambiguous=true when no managed providers exist.
func TestFinalBlocker1_DestinationAmbiguous_ZeroManagedProviders(t *testing.T) {
	db := SetupCompatibilityTestDB(t)

	// Create a non-managed provider
	provider := &models.NotificationProvider{
		Name:                    "Regular Provider",
		Type:                    "webhook",
		Enabled:                 true,
		ManagedLegacySecurity:   false,
		NotifySecurityWAFBlocks: 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)

	// Zero managed providers should result in ambiguous=true
	assert.True(t, response.DestinationAmbiguous, "destination_ambiguous should be true when zero managed providers exist")
	assert.Empty(t, response.WebhookURL, "No destination should be reported")
}

// TestFinalBlocker1_DestinationAmbiguous_OneManagedProvider tests that destination_ambiguous=false when exactly one managed provider exists.
func TestFinalBlocker1_DestinationAmbiguous_OneManagedProvider(t *testing.T) {
	db := SetupCompatibilityTestDB(t)

	// Create one managed provider
	provider := &models.NotificationProvider{
		Name:                    "Managed Provider",
		Type:                    "webhook",
		URL:                     "https://example.com/webhook",
		Enabled:                 true,
		ManagedLegacySecurity:   true,
		NotifySecurityWAFBlocks: 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)

	// Exactly one managed provider should result in ambiguous=false
	assert.False(t, response.DestinationAmbiguous, "destination_ambiguous should be false when exactly one managed provider exists")
	assert.Equal(t, "https://example.com/webhook", response.WebhookURL, "Destination should be reported")
}

// TestFinalBlocker1_DestinationAmbiguous_MultipleManagedProviders tests that destination_ambiguous=true when multiple managed providers exist.
func TestFinalBlocker1_DestinationAmbiguous_MultipleManagedProviders(t *testing.T) {
	db := SetupCompatibilityTestDB(t)

	// Create two managed providers
	provider1 := &models.NotificationProvider{
		Name:                    "Managed Provider 1",
		Type:                    "webhook",
		URL:                     "https://example.com/webhook1",
		Enabled:                 true,
		ManagedLegacySecurity:   true,
		NotifySecurityWAFBlocks: true,
	}
	provider2 := &models.NotificationProvider{
		Name:                    "Managed Provider 2",
		Type:                    "discord",
		URL:                     "https://discord.com/webhook",
		Enabled:                 true,
		ManagedLegacySecurity:   true,
		NotifySecurityACLDenies: 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)

	// Multiple managed providers should result in ambiguous=true
	assert.True(t, response.DestinationAmbiguous, "destination_ambiguous should be true when multiple managed providers exist")
	assert.Empty(t, response.WebhookURL, "No single destination should be reported")
	assert.Empty(t, response.DiscordWebhookURL, "No single destination should be reported")
}

// TestFinalBlocker2_TokenNotExposed tests that provider tokens are not exposed in API responses.
func TestFinalBlocker2_TokenNotExposed(t *testing.T) {
	db := SetupCompatibilityTestDB(t)

	// Create a gotify provider with token
	provider := &models.NotificationProvider{
		Name:                    "Gotify Provider",
		Type:                    "gotify",
		URL:                     "https://gotify.example.com",
		Token:                   "secret_gotify_token_12345",
		Enabled:                 true,
		NotifySecurityWAFBlocks: true,
	}
	require.NoError(t, db.Create(provider).Error)

	// Fetch provider via API simulation
	var fetchedProvider models.NotificationProvider
	err := db.First(&fetchedProvider, "id = ?", provider.ID).Error
	require.NoError(t, err)

	// Serialize to JSON (simulating API response)
	jsonBytes, err := json.Marshal(fetchedProvider)
	require.NoError(t, err)

	// Parse back to map to check field presence
	var response map[string]interface{}
	err = json.Unmarshal(jsonBytes, &response)
	require.NoError(t, err)

	// Token field should NOT be present in JSON
	_, tokenExists := response["token"]
	assert.False(t, tokenExists, "Token field should not be present in JSON response (json:\"-\" tag)")

	// But Token should still be accessible in Go code
	assert.Equal(t, "secret_gotify_token_12345", fetchedProvider.Token, "Token should still be accessible in Go code")
}

// TestFinalBlocker3_SupportedProviderTypes_WebhookDiscordSlackGotifyOnly tests that only supported types are processed.
func TestFinalBlocker3_SupportedProviderTypes_WebhookDiscordSlackGotifyOnly(t *testing.T) {
	db := SetupCompatibilityTestDB(t)

	// Create providers of various types
	supportedTypes := []string{"webhook", "discord", "slack", "gotify"}
	unsupportedTypes := []string{"telegram", "generic", "unknown"}

	// Create supported providers
	for _, providerType := range supportedTypes {
		provider := &models.NotificationProvider{
			Name:                    providerType + " provider",
			Type:                    providerType,
			Enabled:                 true,
			NotifySecurityWAFBlocks: true,
		}
		require.NoError(t, db.Create(provider).Error)
	}

	// Create unsupported providers
	for _, providerType := range unsupportedTypes {
		provider := &models.NotificationProvider{
			Name:                    providerType + " provider",
			Type:                    providerType,
			Enabled:                 true,
			NotifySecurityWAFBlocks: 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)

	// WAF should be enabled because supported types have it enabled
	// Unsupported types should be filtered out and not affect the aggregation
	assert.True(t, response.NotifyWAFBlocks, "WAF should be enabled (supported providers have it enabled)")
}

// TestFinalBlocker3_SupportedProviderTypes_UnsupportedTypesIgnored tests that unsupported types are completely filtered out.
func TestFinalBlocker3_SupportedProviderTypes_UnsupportedTypesIgnored(t *testing.T) {
	db := SetupCompatibilityTestDB(t)

	// Create ONLY unsupported providers
	unsupportedTypes := []string{"sms", "generic"}

	for _, providerType := range unsupportedTypes {
		provider := &models.NotificationProvider{
			Name:                    providerType + " provider",
			Type:                    providerType,
			Enabled:                 true,
			NotifySecurityWAFBlocks: true,
			NotifySecurityACLDenies: 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)

	// All providers are unsupported, so all flags should be false
	assert.False(t, response.NotifyWAFBlocks, "WAF should be disabled (no supported providers)")
	assert.False(t, response.NotifyACLDenies, "ACL should be disabled (no supported providers)")
	assert.False(t, response.NotifyRateLimitHits, "Rate limit should be disabled (no supported providers)")
}

// TestBlocker2_GETReturnsSecurityFields tests GET returns security_* fields per spec.
// Blocker 2: Compatibility endpoint contract must use explicit security_* payload fields per spec.
func TestBlocker2_GETReturnsSecurityFields(t *testing.T) {
	db := SetupCompatibilityTestDB(t)

	provider := &models.NotificationProvider{
		Name:                        "Test Provider",
		Type:                        "webhook",
		Enabled:                     true,
		NotifySecurityWAFBlocks:     true,
		NotifySecurityACLDenies:     false,
		NotifySecurityRateLimitHits: 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)

	// Blocker 2 Fix: Verify API returns security_* field names per spec
	var rawResponse map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &rawResponse)
	require.NoError(t, err)

	// Check field names in JSON (not Go struct field names)
	assert.Equal(t, true, rawResponse["security_waf_enabled"], "API should return security_waf_enabled=true")
	assert.Equal(t, false, rawResponse["security_acl_enabled"], "API should return security_acl_enabled=false")
	assert.Equal(t, true, rawResponse["security_rate_limit_enabled"], "API should return security_rate_limit_enabled=true")

	// Verify notify_* fields are NOT present (spec drift check)
	_, hasNotifyWAF := rawResponse["notify_waf_blocks"]
	assert.False(t, hasNotifyWAF, "API should NOT expose notify_waf_blocks (use security_waf_enabled)")
}

// TestBlocker2_GotifyTokenNeverExposed_Legacy tests that gotify token is never exposed in GET responses.
func TestBlocker2_GotifyTokenNeverExposed_Legacy(t *testing.T) {
	db := SetupCompatibilityTestDB(t)

	// Create gotify provider with token
	provider := &models.NotificationProvider{
		Name:                    "Gotify Provider",
		Type:                    "gotify",
		URL:                     "https://gotify.example.com",
		Token:                   "secret_gotify_token_xyz",
		Enabled:                 true,
		ManagedLegacySecurity:   true,
		NotifySecurityWAFBlocks: 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)

	// Blocker 2: Gotify token must NEVER be exposed in GET responses
	assert.Empty(t, response.GotifyToken, "Gotify token must not be exposed in GET response")
	assert.Equal(t, "https://gotify.example.com", response.GotifyURL, "Gotify URL should be returned")
}

// TestBlocker3_PUTIdempotency tests that identical PUT requests do not mutate timestamps.
func TestBlocker3_PUTIdempotency(t *testing.T) {
	db := SetupCompatibilityTestDB(t)

	// Create managed provider
	managed := &models.NotificationProvider{
		Name:                        "Migrated Security Notifications (Legacy)",
		Type:                        "webhook",
		Enabled:                     true,
		ManagedLegacySecurity:       true,
		NotifySecurityWAFBlocks:     true,
		NotifySecurityACLDenies:     false,
		NotifySecurityRateLimitHits: true,
	}
	require.NoError(t, db.Create(managed).Error)

	// Read initial timestamps
	var initial models.NotificationProvider
	require.NoError(t, db.First(&initial, "id = ?", managed.ID).Error)
	initialUpdatedAt := initial.UpdatedAt

	service := services.NewEnhancedSecurityNotificationService(db)

	// Perform identical PUT
	req := &models.NotificationConfig{
		NotifyWAFBlocks:     true,
		NotifyACLDenies:     false,
		NotifyRateLimitHits: true,
	}
	err := service.UpdateSettings(req)
	require.NoError(t, err)

	// Read timestamps again
	var afterPUT models.NotificationProvider
	require.NoError(t, db.First(&afterPUT, "id = ?", managed.ID).Error)

	// Blocker 3: Timestamps must NOT change when effective values are identical
	assert.Equal(t, initialUpdatedAt.Unix(), afterPUT.UpdatedAt.Unix(), "UpdatedAt should not change for identical PUT")
}

// TestBlocker4_MultipleManagedProvidersAllowed tests that multiple managed providers are updated (not 409).
func TestBlocker4_MultipleManagedProvidersAllowed(t *testing.T) {
	db := SetupCompatibilityTestDB(t)

	// Create two managed providers (simulating migration state)
	managed1 := &models.NotificationProvider{
		Name:                        "Managed Provider 1",
		Type:                        "webhook",
		Enabled:                     true,
		ManagedLegacySecurity:       true,
		NotifySecurityWAFBlocks:     false,
		NotifySecurityACLDenies:     false,
		NotifySecurityRateLimitHits: false,
	}
	managed2 := &models.NotificationProvider{
		Name:                        "Managed Provider 2",
		Type:                        "discord",
		Enabled:                     true,
		ManagedLegacySecurity:       true,
		NotifySecurityWAFBlocks:     false,
		NotifySecurityACLDenies:     false,
		NotifySecurityRateLimitHits: false,
	}
	require.NoError(t, db.Create(managed1).Error)
	require.NoError(t, db.Create(managed2).Error)

	service := services.NewEnhancedSecurityNotificationService(db)

	// Perform PUT - should update ALL managed providers (not 409)
	req := &models.NotificationConfig{
		NotifyWAFBlocks:     true,
		NotifyACLDenies:     true,
		NotifyRateLimitHits: false,
	}
	err := service.UpdateSettings(req)
	require.NoError(t, err, "Multiple managed providers should be allowed and updated")

	// Verify both providers were updated
	var updated1, updated2 models.NotificationProvider
	require.NoError(t, db.First(&updated1, "id = ?", managed1.ID).Error)
	require.NoError(t, db.First(&updated2, "id = ?", managed2.ID).Error)

	assert.True(t, updated1.NotifySecurityWAFBlocks)
	assert.True(t, updated1.NotifySecurityACLDenies)
	assert.False(t, updated1.NotifySecurityRateLimitHits)

	assert.True(t, updated2.NotifySecurityWAFBlocks)
	assert.True(t, updated2.NotifySecurityACLDenies)
	assert.False(t, updated2.NotifySecurityRateLimitHits)
}

// TestBlocker1_FeatureFlagDefaultProduction tests that prod environment defaults to false.
func TestBlocker1_FeatureFlagDefaultProduction(t *testing.T) {
	db := SetupCompatibilityTestDB(t)

	// Clear the auto-created feature flag
	db.Where("key = ?", "feature.notifications.security_provider_events.enabled").Delete(&models.Setting{})

	// Set CHARON_ENV=production
	require.NoError(t, os.Setenv("CHARON_ENV", "production"))
	defer func() { _ = os.Unsetenv("CHARON_ENV") }()

	service := services.NewEnhancedSecurityNotificationService(db)

	// Trigger flag initialization
	_, err := service.GetSettings()
	require.NoError(t, err)

	// Verify flag was created with false default
	var setting models.Setting
	err = db.Where("key = ?", "feature.notifications.security_provider_events.enabled").First(&setting).Error
	require.NoError(t, err)

	assert.Equal(t, "false", setting.Value, "Production must default to false")
}

// TestBlocker1_FeatureFlagDefaultProductionUnsetEnv tests prod default when no env vars set.
func TestBlocker1_FeatureFlagDefaultProductionUnsetEnv(t *testing.T) {
	db := SetupCompatibilityTestDB(t)

	// Clear the auto-created feature flag
	db.Where("key = ?", "feature.notifications.security_provider_events.enabled").Delete(&models.Setting{})

	// Clear both CHARON_ENV and GIN_MODE to simulate production without explicit env vars
	_ = os.Unsetenv("CHARON_ENV")
	_ = os.Unsetenv("GIN_MODE")

	service := services.NewEnhancedSecurityNotificationService(db)

	// Trigger flag initialization
	_, err := service.GetSettings()
	require.NoError(t, err)

	// Blocker 1 Fix: When no env vars set, must default to false (production)
	var setting models.Setting
	err = db.Where("key = ?", "feature.notifications.security_provider_events.enabled").First(&setting).Error
	require.NoError(t, err)

	assert.Equal(t, "false", setting.Value, "Unset env vars must default to false (production)")
}

// TestBlocker1_FeatureFlagDefaultDev tests that dev/test environment defaults to true.
func TestBlocker1_FeatureFlagDefaultDev(t *testing.T) {
	db := SetupCompatibilityTestDB(t)

	// Clear the auto-created feature flag
	db.Where("key = ?", "feature.notifications.security_provider_events.enabled").Delete(&models.Setting{})

	// Set GIN_MODE=test to simulate test environment
	require.NoError(t, os.Setenv("GIN_MODE", "test"))
	defer func() { _ = os.Unsetenv("GIN_MODE") }()

	// Ensure CHARON_ENV is not set to production
	_ = os.Unsetenv("CHARON_ENV")

	service := services.NewEnhancedSecurityNotificationService(db)

	// Trigger flag initialization
	_, err := service.GetSettings()
	require.NoError(t, err)

	// Verify flag was created with true default (test mode)
	var setting models.Setting
	err = db.Where("key = ?", "feature.notifications.security_provider_events.enabled").First(&setting).Error
	require.NoError(t, err)

	assert.Equal(t, "true", setting.Value, "Dev/test must default to true")
}
