package caddy

import (
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/Wikid82/charon/backend/internal/models"
)

// TestBuildWAFHandler_RulesetSelectionPriority verifies the priority order:
// 1. secCfg.WAFRulesSource (user's global choice)
// 2. hostRulesetName from advanced_config
// 3. host.Application
// 4. owasp-crs fallback
func TestBuildWAFHandler_RulesetSelectionPriority(t *testing.T) {
	tests := []struct {
		name            string
		host            *models.ProxyHost
		rulesets        []models.SecurityRuleSet
		rulesetPaths    map[string]string
		secCfg          *models.SecurityConfig
		wafEnabled      bool
		expectedInclude string // Expected substring in directives, empty if handler should be nil
	}{
		{
			name:     "WAFRulesSource takes priority over owasp-crs",
			host:     &models.ProxyHost{UUID: "test-host"},
			rulesets: []models.SecurityRuleSet{{Name: "owasp-crs"}, {Name: "custom-xss"}},
			rulesetPaths: map[string]string{
				"owasp-crs":  "/app/data/rulesets/owasp-crs.conf",
				"custom-xss": "/app/data/rulesets/custom-xss.conf",
			},
			secCfg:          &models.SecurityConfig{WAFMode: "block", WAFRulesSource: "custom-xss"},
			wafEnabled:      true,
			expectedInclude: "custom-xss.conf",
		},
		{
			name: "hostRulesetName takes priority over owasp-crs",
			host: &models.ProxyHost{
				UUID:           "test-host",
				AdvancedConfig: `{"ruleset_name":"per-host-rules"}`,
			},
			rulesets: []models.SecurityRuleSet{{Name: "owasp-crs"}, {Name: "per-host-rules"}},
			rulesetPaths: map[string]string{
				"owasp-crs":      "/app/data/rulesets/owasp-crs.conf",
				"per-host-rules": "/app/data/rulesets/per-host-rules.conf",
			},
			secCfg:          &models.SecurityConfig{WAFMode: "block"},
			wafEnabled:      true,
			expectedInclude: "per-host-rules.conf",
		},
		{
			name: "host.Application takes priority over owasp-crs",
			host: &models.ProxyHost{
				UUID:        "test-host",
				Application: "wordpress",
			},
			rulesets: []models.SecurityRuleSet{{Name: "owasp-crs"}, {Name: "wordpress"}},
			rulesetPaths: map[string]string{
				"owasp-crs": "/app/data/rulesets/owasp-crs.conf",
				"wordpress": "/app/data/rulesets/wordpress.conf",
			},
			secCfg:          &models.SecurityConfig{WAFMode: "block"},
			wafEnabled:      true,
			expectedInclude: "wordpress.conf",
		},
		{
			name:     "owasp-crs used as fallback when no other match",
			host:     &models.ProxyHost{UUID: "test-host"},
			rulesets: []models.SecurityRuleSet{{Name: "owasp-crs"}, {Name: "unrelated-rules"}},
			rulesetPaths: map[string]string{
				"owasp-crs":       "/app/data/rulesets/owasp-crs.conf",
				"unrelated-rules": "/app/data/rulesets/unrelated.conf",
			},
			secCfg:          &models.SecurityConfig{WAFMode: "block"},
			wafEnabled:      true,
			expectedInclude: "owasp-crs.conf",
		},
		{
			name: "WAFRulesSource takes priority over host.Application and owasp-crs",
			host: &models.ProxyHost{
				UUID:        "test-host",
				Application: "wordpress",
			},
			rulesets: []models.SecurityRuleSet{{Name: "owasp-crs"}, {Name: "wordpress"}, {Name: "global-custom"}},
			rulesetPaths: map[string]string{
				"owasp-crs":     "/app/data/rulesets/owasp-crs.conf",
				"wordpress":     "/app/data/rulesets/wordpress.conf",
				"global-custom": "/app/data/rulesets/global-custom.conf",
			},
			secCfg:          &models.SecurityConfig{WAFMode: "block", WAFRulesSource: "global-custom"},
			wafEnabled:      true,
			expectedInclude: "global-custom.conf",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			handler, err := buildWAFHandler(tc.host, tc.rulesets, tc.rulesetPaths, tc.secCfg, tc.wafEnabled)
			require.NoError(t, err)

			if tc.expectedInclude == "" {
				require.Nil(t, handler)
				return
			}

			require.NotNil(t, handler)
			directives, ok := handler["directives"].(string)
			require.True(t, ok, "directives should be a string")
			require.Contains(t, directives, tc.expectedInclude)
		})
	}
}

// TestBuildWAFHandler_NoDirectivesReturnsNil verifies that the handler returns nil
// when no directives can be set (Bug fix #2 from the plan)
func TestBuildWAFHandler_NoDirectivesReturnsNil(t *testing.T) {
	tests := []struct {
		name         string
		host         *models.ProxyHost
		rulesets     []models.SecurityRuleSet
		rulesetPaths map[string]string
		secCfg       *models.SecurityConfig
		wafEnabled   bool
	}{
		{
			name:         "Empty rulesets returns nil",
			host:         &models.ProxyHost{UUID: "test-host"},
			rulesets:     []models.SecurityRuleSet{},
			rulesetPaths: map[string]string{},
			secCfg:       &models.SecurityConfig{WAFMode: "block"},
			wafEnabled:   true,
		},
		{
			name:     "Ruleset exists but no path mapping returns nil",
			host:     &models.ProxyHost{UUID: "test-host"},
			rulesets: []models.SecurityRuleSet{{Name: "my-rules"}},
			rulesetPaths: map[string]string{
				"other-rules": "/path/to/other.conf", // Path for different ruleset
			},
			secCfg:     &models.SecurityConfig{WAFMode: "block"},
			wafEnabled: true,
		},
		{
			name:         "WAFRulesSource specified but not in rulesets or paths returns nil",
			host:         &models.ProxyHost{UUID: "test-host"},
			rulesets:     []models.SecurityRuleSet{{Name: "other-rules"}},
			rulesetPaths: map[string]string{},
			secCfg:       &models.SecurityConfig{WAFMode: "block", WAFRulesSource: "nonexistent"},
			wafEnabled:   true,
		},
		{
			name:     "Empty path in rulesetPaths returns nil",
			host:     &models.ProxyHost{UUID: "test-host"},
			rulesets: []models.SecurityRuleSet{{Name: "owasp-crs"}},
			rulesetPaths: map[string]string{
				"owasp-crs": "", // Empty path
			},
			secCfg:     &models.SecurityConfig{WAFMode: "block"},
			wafEnabled: true,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			handler, err := buildWAFHandler(tc.host, tc.rulesets, tc.rulesetPaths, tc.secCfg, tc.wafEnabled)
			require.NoError(t, err)
			require.Nil(t, handler, "Handler should be nil when no directives can be set")
		})
	}
}

// TestBuildWAFHandler_DisabledModes verifies WAF is disabled correctly
func TestBuildWAFHandler_DisabledModes(t *testing.T) {
	rulesets := []models.SecurityRuleSet{{Name: "owasp-crs"}}
	rulesetPaths := map[string]string{"owasp-crs": "/path/to/rules.conf"}
	host := &models.ProxyHost{UUID: "test-host"}

	tests := []struct {
		name       string
		secCfg     *models.SecurityConfig
		wafEnabled bool
	}{
		{
			name:       "wafEnabled false returns nil",
			secCfg:     &models.SecurityConfig{WAFMode: "block"},
			wafEnabled: false,
		},
		{
			name:       "WAFMode disabled returns nil",
			secCfg:     &models.SecurityConfig{WAFMode: "disabled"},
			wafEnabled: true,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			handler, err := buildWAFHandler(host, rulesets, rulesetPaths, tc.secCfg, tc.wafEnabled)
			require.NoError(t, err)
			require.Nil(t, handler)
		})
	}
}

// TestBuildWAFHandler_HandlerStructure verifies the JSON structure matches the Handoff Contract
func TestBuildWAFHandler_HandlerStructure(t *testing.T) {
	host := &models.ProxyHost{UUID: "test-host"}
	rulesets := []models.SecurityRuleSet{{Name: "integration-xss"}}
	rulesetPaths := map[string]string{
		"integration-xss": "/app/data/caddy/coraza/rulesets/integration-xss-a1b2c3d4.conf",
	}
	secCfg := &models.SecurityConfig{WAFMode: "block", WAFRulesSource: "integration-xss"}

	handler, err := buildWAFHandler(host, rulesets, rulesetPaths, secCfg, true)
	require.NoError(t, err)
	require.NotNil(t, handler)

	// Verify handler type
	require.Equal(t, "waf", handler["handler"])

	// Verify directives contain Include statement
	directives, ok := handler["directives"].(string)
	require.True(t, ok)
	require.Contains(t, directives, "Include /app/data/caddy/coraza/rulesets/integration-xss-a1b2c3d4.conf")

	// Verify directives contain expected ModSecurity directives
	require.Contains(t, directives, "SecRuleEngine On")
	require.Contains(t, directives, "SecRequestBodyAccess On")

	// Verify JSON marshaling produces expected structure
	jsonBytes, err := json.Marshal(handler)
	require.NoError(t, err)
	require.Contains(t, string(jsonBytes), `"handler":"waf"`)
	require.Contains(t, string(jsonBytes), `"directives":"SecRuleEngine`)
}

// TestBuildWAFHandler_AdvancedConfigParsing verifies advanced_config JSON parsing
func TestBuildWAFHandler_AdvancedConfigParsing(t *testing.T) {
	rulesets := []models.SecurityRuleSet{
		{Name: "owasp-crs"},
		{Name: "custom-ruleset"},
	}
	rulesetPaths := map[string]string{
		"owasp-crs":      "/path/owasp.conf",
		"custom-ruleset": "/path/custom.conf",
	}
	secCfg := &models.SecurityConfig{WAFMode: "block"}

	tests := []struct {
		name            string
		advancedConfig  string
		expectedInclude string
	}{
		{
			name:            "Valid ruleset_name in advanced_config",
			advancedConfig:  `{"ruleset_name":"custom-ruleset"}`,
			expectedInclude: "custom.conf",
		},
		{
			name:            "Invalid JSON falls back to owasp-crs",
			advancedConfig:  `{invalid json`,
			expectedInclude: "owasp.conf",
		},
		{
			name:            "Empty advanced_config falls back to owasp-crs",
			advancedConfig:  "",
			expectedInclude: "owasp.conf",
		},
		{
			name:            "Empty ruleset_name string falls back to owasp-crs",
			advancedConfig:  `{"ruleset_name":""}`,
			expectedInclude: "owasp.conf",
		},
		{
			name:            "Non-string ruleset_name falls back to owasp-crs",
			advancedConfig:  `{"ruleset_name":123}`,
			expectedInclude: "owasp.conf",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			host := &models.ProxyHost{
				UUID:           "test-host",
				AdvancedConfig: tc.advancedConfig,
			}
			handler, err := buildWAFHandler(host, rulesets, rulesetPaths, secCfg, true)
			require.NoError(t, err)
			require.NotNil(t, handler)
			directives := handler["directives"].(string)
			require.Contains(t, directives, tc.expectedInclude)
		})
	}
}
