package caddy

import (
	"encoding/json"
	"strconv"
	"testing"

	"github.com/stretchr/testify/require"

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

func TestGenerateConfig_Empty(t *testing.T) {
	config, err := GenerateConfig([]models.ProxyHost{}, "/tmp/caddy-data", "admin@example.com", "", "", false, false, false, false, true, "", nil, nil, nil, nil)
	require.NoError(t, err)
	require.NotNil(t, config.Apps.HTTP)
	require.Empty(t, config.Apps.HTTP.Servers)
	require.NotNil(t, config)
	require.NotNil(t, config.Apps.HTTP)
	require.Empty(t, config.Apps.HTTP.Servers)
}

func TestGenerateConfig_SingleHost(t *testing.T) {
	hosts := []models.ProxyHost{
		{
			UUID:             "test-uuid",
			Name:             "Media",
			DomainNames:      "media.example.com",
			ForwardScheme:    "http",
			ForwardHost:      "media",
			ForwardPort:      32400,
			SSLForced:        true,
			WebsocketSupport: false,
			Enabled:          true,
		},
	}

	config, err := GenerateConfig(hosts, "/tmp/caddy-data", "admin@example.com", "", "", false, false, false, false, true, "", nil, nil, nil, nil)
	require.NoError(t, err)
	require.NotNil(t, config.Apps.HTTP)
	require.Len(t, config.Apps.HTTP.Servers, 1)
	require.NotNil(t, config)
	require.NotNil(t, config.Apps.HTTP)
	require.Len(t, config.Apps.HTTP.Servers, 1)

	server := config.Apps.HTTP.Servers["charon_server"]
	require.NotNil(t, server)
	require.Contains(t, server.Listen, ":80")
	require.Contains(t, server.Listen, ":443")
	require.Len(t, server.Routes, 1)

	route := server.Routes[0]
	require.Len(t, route.Match, 1)
	require.Equal(t, []string{"media.example.com"}, route.Match[0].Host)
	require.Len(t, route.Handle, 1)
	require.True(t, route.Terminal)

	handler := route.Handle[0]
	require.Equal(t, "reverse_proxy", handler["handler"])
}

func TestGenerateConfig_MultipleHosts(t *testing.T) {
	hosts := []models.ProxyHost{
		{
			UUID:        "uuid-1",
			DomainNames: "site1.example.com",
			ForwardHost: "app1",
			ForwardPort: 8080,
			Enabled:     true,
		},
		{
			UUID:        "uuid-2",
			DomainNames: "site2.example.com",
			ForwardHost: "app2",
			ForwardPort: 8081,
			Enabled:     true,
		},
	}

	config, err := GenerateConfig(hosts, "/tmp/caddy-data", "admin@example.com", "", "", false, false, false, false, true, "", nil, nil, nil, nil)
	require.NoError(t, err)
	require.Len(t, config.Apps.HTTP.Servers["charon_server"].Routes, 2)
	require.Len(t, config.Apps.HTTP.Servers["charon_server"].Routes, 2)
}

func TestGenerateConfig_WebSocketEnabled(t *testing.T) {
	hosts := []models.ProxyHost{
		{
			UUID:             "uuid-ws",
			DomainNames:      "ws.example.com",
			ForwardHost:      "wsapp",
			ForwardPort:      3000,
			WebsocketSupport: true,
			Enabled:          true,
		},
	}
	config, err := GenerateConfig(hosts, "/tmp/caddy-data", "admin@example.com", "", "", false, false, false, false, true, "", nil, nil, nil, nil)
	require.NoError(t, err)
	require.NotNil(t, config.Apps.HTTP)

	route := config.Apps.HTTP.Servers["charon_server"].Routes[0]
	handler := route.Handle[0]

	// Check WebSocket headers are present
	require.NotNil(t, handler["headers"])
}

func TestGenerateConfig_EmptyDomain(t *testing.T) {
	hosts := []models.ProxyHost{
		{
			UUID:        "bad-uuid",
			DomainNames: "",
			ForwardHost: "app",
			ForwardPort: 8080,
			Enabled:     true,
		},
	}

	config, err := GenerateConfig(hosts, "/tmp/caddy-data", "admin@example.com", "", "", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)
	require.Empty(t, config.Apps.HTTP.Servers["charon_server"].Routes)
	// Should produce empty routes (or just catch-all if frontendDir was set, but it's empty here)
	require.Empty(t, config.Apps.HTTP.Servers["charon_server"].Routes)
}

func TestGenerateConfig_Logging(t *testing.T) {
	hosts := []models.ProxyHost{}
	config, err := GenerateConfig(hosts, "/tmp/caddy-data", "admin@example.com", "", "", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)
	require.NotNil(t, config.Logging)

	// Verify logging configuration
	require.NotNil(t, config.Logging)
	require.NotNil(t, config.Logging.Logs)
	require.NotNil(t, config.Logging.Logs["access"])
	require.Equal(t, "INFO", config.Logging.Logs["access"].Level)
	require.Contains(t, config.Logging.Logs["access"].Writer.Filename, "access.log")
	require.Equal(t, 10, config.Logging.Logs["access"].Writer.RollSize)
	require.Equal(t, 5, config.Logging.Logs["access"].Writer.RollKeep)
	require.Equal(t, 7, config.Logging.Logs["access"].Writer.RollKeepDays)
}

func TestGenerateConfig_IPHostsSkipAutoHTTPS(t *testing.T) {
	hosts := []models.ProxyHost{
		{
			UUID:        "uuid-ip",
			DomainNames: "192.0.2.10",
			ForwardHost: "app",
			ForwardPort: 8080,
			Enabled:     true,
		},
	}

	config, err := GenerateConfig(hosts, "/tmp/caddy-data", "admin@example.com", "", "", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)

	server := config.Apps.HTTP.Servers["charon_server"]
	require.NotNil(t, server)
	require.Contains(t, server.AutoHTTPS.Skip, "192.0.2.10")

	// Ensure TLS automation adds internal issuer for IP literals
	require.NotNil(t, config.Apps.TLS)
	require.NotNil(t, config.Apps.TLS.Automation)
	require.GreaterOrEqual(t, len(config.Apps.TLS.Automation.Policies), 1)
	foundIPPolicy := false
	for _, p := range config.Apps.TLS.Automation.Policies {
		if len(p.Subjects) == 0 {
			continue
		}
		if p.Subjects[0] == "192.0.2.10" {
			foundIPPolicy = true
			require.Len(t, p.IssuersRaw, 1)
			issuer := p.IssuersRaw[0].(map[string]interface{})
			require.Equal(t, "internal", issuer["module"])
		}
	}
	require.True(t, foundIPPolicy, "expected internal issuer policy for IP host")
}

func TestGenerateConfig_Advanced(t *testing.T) {
	hosts := []models.ProxyHost{
		{
			UUID:           "advanced-uuid",
			Name:           "Advanced",
			DomainNames:    "advanced.example.com",
			ForwardScheme:  "http",
			ForwardHost:    "advanced",
			ForwardPort:    8080,
			SSLForced:      true,
			HSTSEnabled:    true,
			HSTSSubdomains: true,
			BlockExploits:  true,
			Enabled:        true,
			Locations: []models.Location{
				{
					Path:        "/api",
					ForwardHost: "api-service",
					ForwardPort: 9000,
				},
			},
		},
	}

	config, err := GenerateConfig(hosts, "/tmp/caddy-data", "admin@example.com", "", "", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)
	require.NotNil(t, config)
	require.NotNil(t, config)

	server := config.Apps.HTTP.Servers["charon_server"]
	require.NotNil(t, server)
	// Should have 2 routes: 1 for location /api, 1 for main domain
	require.Len(t, server.Routes, 2)

	// Check Location Route (should be first as it is more specific)
	locRoute := server.Routes[0]
	require.Equal(t, []string{"/api", "/api/*"}, locRoute.Match[0].Path)
	require.Equal(t, []string{"advanced.example.com"}, locRoute.Match[0].Host)

	// Check Main Route
	mainRoute := server.Routes[1]
	require.Nil(t, mainRoute.Match[0].Path) // No path means all paths
	require.Equal(t, []string{"advanced.example.com"}, mainRoute.Match[0].Host)

	// Check HSTS and BlockExploits handlers in main route
	// Handlers are: [HSTS, BlockExploits, ReverseProxy]
	// But wait, BlockExploitsHandler implementation details?
	// Let's just check count for now or inspect types if possible.
	// Based on code:
	// handlers = append(handlers, HeaderHandler(...)) // HSTS
	// handlers = append(handlers, BlockExploitsHandler()) // BlockExploits
	// mainHandlers = append(handlers, ReverseProxyHandler(...))

	require.Len(t, mainRoute.Handle, 3)

	// Check HSTS
	hstsHandler := mainRoute.Handle[0]
	require.Equal(t, "headers", hstsHandler["handler"])
}

func TestGenerateConfig_ACMEStaging(t *testing.T) {
	hosts := []models.ProxyHost{
		{
			UUID:        "test-uuid",
			DomainNames: "test.example.com",
			ForwardHost: "app",
			ForwardPort: 8080,
			Enabled:     true,
		},
	}

	// Test with staging enabled
	config, err := GenerateConfig(hosts, "/tmp/caddy-data", "admin@example.com", "", "letsencrypt", true, false, false, false, true, "", nil, nil, nil, nil)
	require.NoError(t, err)
	require.NotNil(t, config.Apps.TLS)
	require.NotNil(t, config.Apps.TLS)
	require.NotNil(t, config.Apps.TLS.Automation)
	require.Len(t, config.Apps.TLS.Automation.Policies, 1)

	issuers := config.Apps.TLS.Automation.Policies[0].IssuersRaw
	require.Len(t, issuers, 1)

	acmeIssuer := issuers[0].(map[string]interface{})
	require.Equal(t, "acme", acmeIssuer["module"])
	require.Equal(t, "admin@example.com", acmeIssuer["email"])
	require.Equal(t, "https://acme-staging-v02.api.letsencrypt.org/directory", acmeIssuer["ca"])

	// Test with staging disabled (production)
	config, err = GenerateConfig(hosts, "/tmp/caddy-data", "admin@example.com", "", "letsencrypt", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)
	require.NotNil(t, config.Apps.TLS)
	require.NotNil(t, config.Apps.TLS.Automation)
	require.Len(t, config.Apps.TLS.Automation.Policies, 1)

	issuers = config.Apps.TLS.Automation.Policies[0].IssuersRaw
	require.Len(t, issuers, 1)

	acmeIssuer = issuers[0].(map[string]interface{})
	require.Equal(t, "acme", acmeIssuer["module"])
	require.Equal(t, "admin@example.com", acmeIssuer["email"])
	_, hasCA := acmeIssuer["ca"]
	require.False(t, hasCA, "Production mode should not set ca field (uses default)")
	// We can't easily check the map content without casting, but we know it's there.
}

func TestBuildACLHandler_WhitelistAndBlacklistAdminMerge(t *testing.T) {
	// Whitelist case: ensure adminWhitelist gets merged into allowed ranges
	acl := &models.AccessList{Type: "whitelist", IPRules: `[{"cidr":"127.0.0.1/32"}]`}
	handler, err := buildACLHandler(acl, "10.0.0.1/32")
	require.NoError(t, err)
	// handler should include both ranges in the remote_ip ranges
	b, _ := json.Marshal(handler)
	s := string(b)
	require.Contains(t, s, "127.0.0.1/32")
	require.Contains(t, s, "10.0.0.1/32")

	// Blacklist case: ensure adminWhitelist excluded from match
	acl2 := &models.AccessList{Type: "blacklist", IPRules: `[{"cidr":"1.2.3.0/24"}]`}
	handler2, err := buildACLHandler(acl2, "192.168.0.1/32")
	require.NoError(t, err)
	b2, _ := json.Marshal(handler2)
	s2 := string(b2)
	require.Contains(t, s2, "1.2.3.0/24")
	require.Contains(t, s2, "192.168.0.1/32")
}

func TestBuildACLHandler_GeoAndLocalNetwork(t *testing.T) {
	// Geo whitelist
	acl := &models.AccessList{Type: "geo_whitelist", CountryCodes: "US,CA"}
	h, err := buildACLHandler(acl, "")
	require.NoError(t, err)
	b, _ := json.Marshal(h)
	s := string(b)
	require.Contains(t, s, "geoip2.country_code")

	// Geo blacklist
	acl2 := &models.AccessList{Type: "geo_blacklist", CountryCodes: "RU"}
	h2, err := buildACLHandler(acl2, "")
	require.NoError(t, err)
	b2, _ := json.Marshal(h2)
	s2 := string(b2)
	require.Contains(t, s2, "geoip2.country_code")

	// Local network only
	acl3 := &models.AccessList{Type: "whitelist", LocalNetworkOnly: true}
	h3, err := buildACLHandler(acl3, "")
	require.NoError(t, err)
	b3, _ := json.Marshal(h3)
	s3 := string(b3)
	require.Contains(t, s3, "10.0.0.0/8")
}

func TestBuildACLHandler_AdminWhitelistParsing(t *testing.T) {
	// Whitelist should trim and include multiple values, skip empties
	acl := &models.AccessList{Type: "whitelist", IPRules: `[{"cidr":"127.0.0.1/32"}]`}
	handler, err := buildACLHandler(acl, " , 10.0.0.1/32, , 192.168.1.5/32 ")
	require.NoError(t, err)
	b, _ := json.Marshal(handler)
	s := string(b)
	require.Contains(t, s, "127.0.0.1/32")
	require.Contains(t, s, "10.0.0.1/32")
	require.Contains(t, s, "192.168.1.5/32")

	// Blacklist parsing too
	acl2 := &models.AccessList{Type: "blacklist", IPRules: `[{"cidr":"1.2.3.0/24"}]`}
	handler2, err := buildACLHandler(acl2, " , 192.168.0.1/32, ")
	require.NoError(t, err)
	b2, _ := json.Marshal(handler2)
	s2 := string(b2)
	require.Contains(t, s2, "1.2.3.0/24")
	require.Contains(t, s2, "192.168.0.1/32")
}

func TestBuildRateLimitHandler_Disabled(t *testing.T) {
	// Test nil secCfg returns nil handler
	h, err := buildRateLimitHandler(nil, nil)
	require.NoError(t, err)
	require.Nil(t, h)
}

func TestBuildRateLimitHandler_InvalidValues(t *testing.T) {
	// Test zero requests returns nil handler
	secCfg := &models.SecurityConfig{
		RateLimitRequests:  0,
		RateLimitWindowSec: 60,
	}
	h, err := buildRateLimitHandler(nil, secCfg)
	require.NoError(t, err)
	require.Nil(t, h)

	// Test zero window returns nil handler
	secCfg2 := &models.SecurityConfig{
		RateLimitRequests:  100,
		RateLimitWindowSec: 0,
	}
	h, err = buildRateLimitHandler(nil, secCfg2)
	require.NoError(t, err)
	require.Nil(t, h)

	// Test negative values returns nil handler
	secCfg3 := &models.SecurityConfig{
		RateLimitRequests:  -1,
		RateLimitWindowSec: 60,
	}
	h, err = buildRateLimitHandler(nil, secCfg3)
	require.NoError(t, err)
	require.Nil(t, h)
}

func TestBuildRateLimitHandler_ValidConfig(t *testing.T) {
	// Test valid configuration produces correct caddy-ratelimit format
	secCfg := &models.SecurityConfig{
		RateLimitRequests:  100,
		RateLimitWindowSec: 60,
		RateLimitBurst:     25,
	}
	h, err := buildRateLimitHandler(nil, secCfg)
	require.NoError(t, err)
	require.NotNil(t, h)

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

	// Verify rate_limits structure
	rateLimits, ok := h["rate_limits"].(map[string]interface{})
	require.True(t, ok, "rate_limits should be a map")

	staticZone, ok := rateLimits["static"].(map[string]interface{})
	require.True(t, ok, "static zone should be a map")

	// Verify caddy-ratelimit specific fields
	require.Equal(t, "{http.request.remote.host}", staticZone["key"])
	require.Equal(t, "60s", staticZone["window"])
	require.Equal(t, 100, staticZone["max_events"])
	// Note: caddy-ratelimit doesn't support burst parameter (uses sliding window)
}

func TestBuildRateLimitHandler_JSONFormat(t *testing.T) {
	// Test that the handler produces valid JSON matching caddy-ratelimit schema
	secCfg := &models.SecurityConfig{
		RateLimitRequests:  30,
		RateLimitWindowSec: 10,
		RateLimitBurst:     5,
	}
	h, err := buildRateLimitHandler(nil, secCfg)
	require.NoError(t, err)
	require.NotNil(t, h)

	// Marshal to JSON and verify structure
	b, err := json.Marshal(h)
	require.NoError(t, err)
	s := string(b)

	// Verify expected JSON content
	require.Contains(t, s, `"handler":"rate_limit"`)
	require.Contains(t, s, `"rate_limits"`)
	require.Contains(t, s, `"static"`)
	require.Contains(t, s, `"key":"{http.request.remote.host}"`)
	require.Contains(t, s, `"window":"10s"`)
	require.Contains(t, s, `"max_events":30`)
	// Note: burst field not included (not supported by caddy-ratelimit)
}

func TestGenerateConfig_WithRateLimiting(t *testing.T) {
	// Test that rate limiting is included in generated config when enabled
	hosts := []models.ProxyHost{
		{
			UUID:        "test-uuid",
			DomainNames: "example.com",
			ForwardHost: "app",
			ForwardPort: 8080,
			Enabled:     true,
		},
	}

	secCfg := &models.SecurityConfig{
		RateLimitEnable:    true,
		RateLimitRequests:  60,
		RateLimitWindowSec: 60,
	}

	// rateLimitEnabled=true should include the handler
	config, err := GenerateConfig(hosts, "/tmp/caddy-data", "admin@example.com", "", "", false, false, false, true, false, "", nil, nil, nil, secCfg)
	require.NoError(t, err)
	require.NotNil(t, config.Apps.HTTP)

	server := config.Apps.HTTP.Servers["charon_server"]
	require.NotNil(t, server)
	require.Len(t, server.Routes, 1)

	route := server.Routes[0]
	// Handlers should include rate_limit + reverse_proxy
	require.GreaterOrEqual(t, len(route.Handle), 2)

	// Find the rate_limit handler
	var foundRateLimit bool
	for _, h := range route.Handle {
		if h["handler"] == "rate_limit" {
			foundRateLimit = true
			// Verify it has the correct structure
			require.NotNil(t, h["rate_limits"])
			break
		}
	}
	require.True(t, foundRateLimit, "rate_limit handler should be present")
}

func TestBuildRateLimitHandler_UsesBurst(t *testing.T) {
	// Verify that burst config value is ignored (caddy-ratelimit doesn't support it)
	secCfg := &models.SecurityConfig{
		RateLimitRequests:  100,
		RateLimitWindowSec: 60,
		RateLimitBurst:     50,
	}
	h, err := buildRateLimitHandler(nil, secCfg)
	require.NoError(t, err)
	require.NotNil(t, h)

	// Handler should be a plain rate_limit (no bypass list)
	require.Equal(t, "rate_limit", h["handler"])

	rateLimits, ok := h["rate_limits"].(map[string]interface{})
	require.True(t, ok)
	staticZone, ok := rateLimits["static"].(map[string]interface{})
	require.True(t, ok)

	// Verify burst field is NOT present (not supported by caddy-ratelimit)
	_, hasBurst := staticZone["burst"]
	require.False(t, hasBurst, "burst field should not be included")
}

func TestBuildRateLimitHandler_DefaultBurst(t *testing.T) {
	// Verify that burst field is not included (caddy-ratelimit uses sliding window, no burst)
	secCfg := &models.SecurityConfig{
		RateLimitRequests:  100,
		RateLimitWindowSec: 60,
		RateLimitBurst:     0, // Not set
	}
	h, err := buildRateLimitHandler(nil, secCfg)
	require.NoError(t, err)
	require.NotNil(t, h)

	rateLimits, ok := h["rate_limits"].(map[string]interface{})
	require.True(t, ok)
	staticZone, ok := rateLimits["static"].(map[string]interface{})
	require.True(t, ok)

	// Verify burst field is NOT present
	_, hasBurst := staticZone["burst"]
	require.False(t, hasBurst, "burst field should not be included")

	// Test with small requests value - should also not have burst
	secCfg2 := &models.SecurityConfig{
		RateLimitRequests:  3,
		RateLimitWindowSec: 60,
		RateLimitBurst:     0,
	}
	h2, err := buildRateLimitHandler(nil, secCfg2)
	require.NoError(t, err)
	require.NotNil(t, h2)

	rateLimits2, ok := h2["rate_limits"].(map[string]interface{})
	require.True(t, ok)
	staticZone2, ok := rateLimits2["static"].(map[string]interface{})
	require.True(t, ok)

	// Verify no burst field here either
	_, hasBurst2 := staticZone2["burst"]
	require.False(t, hasBurst2, "burst field should not be included")
}

func TestBuildRateLimitHandler_BypassList(t *testing.T) {
	// Verify bypass list creates subroute structure
	secCfg := &models.SecurityConfig{
		RateLimitRequests:   100,
		RateLimitWindowSec:  60,
		RateLimitBurst:      20,
		RateLimitBypassList: "10.0.0.0/8, 192.168.1.0/24",
	}
	h, err := buildRateLimitHandler(nil, secCfg)
	require.NoError(t, err)
	require.NotNil(t, h)

	// Handler should be a subroute when bypass list is configured
	require.Equal(t, "subroute", h["handler"])

	// Marshal to JSON for easy inspection
	b, err := json.Marshal(h)
	require.NoError(t, err)
	s := string(b)

	// Verify subroute contains bypass IPs
	require.Contains(t, s, "10.0.0.0/8")
	require.Contains(t, s, "192.168.1.0/24")
	require.Contains(t, s, "remote_ip")
	require.Contains(t, s, "rate_limit")
}

func TestBuildRateLimitHandler_BypassList_PlainIPs(t *testing.T) {
	// Verify plain IPs are converted to CIDRs
	secCfg := &models.SecurityConfig{
		RateLimitRequests:   100,
		RateLimitWindowSec:  60,
		RateLimitBurst:      20,
		RateLimitBypassList: "10.0.0.1, 192.168.1.1",
	}
	h, err := buildRateLimitHandler(nil, secCfg)
	require.NoError(t, err)
	require.NotNil(t, h)

	require.Equal(t, "subroute", h["handler"])

	b, err := json.Marshal(h)
	require.NoError(t, err)
	s := string(b)

	// Plain IPs should be converted to /32 CIDRs
	require.Contains(t, s, "10.0.0.1/32")
	require.Contains(t, s, "192.168.1.1/32")
}

func TestBuildRateLimitHandler_BypassList_InvalidEntries(t *testing.T) {
	// Verify invalid entries are ignored
	secCfg := &models.SecurityConfig{
		RateLimitRequests:   100,
		RateLimitWindowSec:  60,
		RateLimitBurst:      20,
		RateLimitBypassList: "invalid, 10.0.0.0/8, also-invalid",
	}
	h, err := buildRateLimitHandler(nil, secCfg)
	require.NoError(t, err)
	require.NotNil(t, h)

	require.Equal(t, "subroute", h["handler"])

	b, err := json.Marshal(h)
	require.NoError(t, err)
	s := string(b)

	// Only valid CIDR should be present
	require.Contains(t, s, "10.0.0.0/8")
	require.NotContains(t, s, "invalid")
	require.NotContains(t, s, "also-invalid")
}

func TestBuildRateLimitHandler_BypassList_Empty(t *testing.T) {
	// Verify empty bypass list returns plain rate_limit handler
	secCfg := &models.SecurityConfig{
		RateLimitRequests:   100,
		RateLimitWindowSec:  60,
		RateLimitBurst:      20,
		RateLimitBypassList: "",
	}
	h, err := buildRateLimitHandler(nil, secCfg)
	require.NoError(t, err)
	require.NotNil(t, h)

	// Should be plain rate_limit, not subroute
	require.Equal(t, "rate_limit", h["handler"])
}

func TestBuildRateLimitHandler_BypassList_AllInvalid(t *testing.T) {
	// Verify all-invalid bypass list returns plain rate_limit handler
	secCfg := &models.SecurityConfig{
		RateLimitRequests:   100,
		RateLimitWindowSec:  60,
		RateLimitBurst:      20,
		RateLimitBypassList: "invalid, also-invalid, not-an-ip",
	}
	h, err := buildRateLimitHandler(nil, secCfg)
	require.NoError(t, err)
	require.NotNil(t, h)

	// Should be plain rate_limit since no valid CIDRs
	require.Equal(t, "rate_limit", h["handler"])
}

func TestParseBypassCIDRs(t *testing.T) {
	// Test various inputs
	tests := []struct {
		name     string
		input    string
		expected []string
	}{
		{"empty", "", nil},
		{"single_cidr", "10.0.0.0/8", []string{"10.0.0.0/8"}},
		{"multiple_cidrs", "10.0.0.0/8, 192.168.0.0/16", []string{"10.0.0.0/8", "192.168.0.0/16"}},
		{"plain_ipv4", "10.0.0.1", []string{"10.0.0.1/32"}},
		{"plain_ipv6", "::1", []string{"::1/128"}},
		{"mixed", "10.0.0.0/8, 192.168.1.1, invalid", []string{"10.0.0.0/8", "192.168.1.1/32"}},
		{"with_spaces", " 10.0.0.0/8 , , 192.168.0.0/16 ", []string{"10.0.0.0/8", "192.168.0.0/16"}},
		{"all_invalid", "invalid, bad-ip", nil},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := parseBypassCIDRs(tt.input)
			if tt.expected == nil {
				require.Nil(t, result)
			} else {
				require.Equal(t, tt.expected, result)
			}
		})
	}
}

// TestBuildWAFHandler_ParanoiaLevel verifies paranoia level is correctly set in directives
func TestBuildWAFHandler_ParanoiaLevel(t *testing.T) {
	rulesetPaths := map[string]string{
		"owasp-crs": "/etc/caddy/rules/owasp-crs.conf",
	}
	rulesets := []models.SecurityRuleSet{
		{Name: "owasp-crs"},
	}

	tests := []struct {
		name           string
		paranoiaLevel  int
		expectedLevel  int
		expectedEngine string
	}{
		{"level_1_default", 0, 1, "On"},
		{"level_1_explicit", 1, 1, "On"},
		{"level_2", 2, 2, "On"},
		{"level_3", 3, 3, "On"},
		{"level_4_max", 4, 4, "On"},
		{"level_invalid_high", 5, 1, "On"}, // Invalid falls back to 1
		{"level_invalid_neg", -1, 1, "On"}, // Invalid falls back to 1
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			secCfg := &models.SecurityConfig{
				WAFMode:          "block",
				WAFParanoiaLevel: tt.paranoiaLevel,
				WAFRulesSource:   "owasp-crs",
			}

			h, err := buildWAFHandler(nil, rulesets, rulesetPaths, secCfg, true)
			require.NoError(t, err)
			require.NotNil(t, h)
			require.Equal(t, "waf", h["handler"])

			directives := h["directives"].(string)
			require.Contains(t, directives, "SecRuleEngine On")
			require.Contains(t, directives, "SecRequestBodyAccess On")
			require.Contains(t, directives, "tx.paranoia_level="+strconv.Itoa(tt.expectedLevel))
		})
	}
}

// TestBuildWAFHandler_Exclusions verifies SecRuleRemoveById directives are generated
func TestBuildWAFHandler_Exclusions(t *testing.T) {
	rulesetPaths := map[string]string{
		"owasp-crs": "/etc/caddy/rules/owasp-crs.conf",
	}
	rulesets := []models.SecurityRuleSet{
		{Name: "owasp-crs"},
	}

	// Test exclusions without targets (full rule removal)
	exclusionsJSON := `[{"rule_id":942100,"description":"SQL Injection rule"},{"rule_id":941100}]`
	secCfg := &models.SecurityConfig{
		WAFMode:        "block",
		WAFRulesSource: "owasp-crs",
		WAFExclusions:  exclusionsJSON,
	}

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

	directives := h["directives"].(string)
	require.Contains(t, directives, "SecRuleRemoveById 942100")
	require.Contains(t, directives, "SecRuleRemoveById 941100")
}

// TestBuildWAFHandler_ExclusionsWithTarget verifies SecRuleUpdateTargetById directives
func TestBuildWAFHandler_ExclusionsWithTarget(t *testing.T) {
	rulesetPaths := map[string]string{
		"owasp-crs": "/etc/caddy/rules/owasp-crs.conf",
	}
	rulesets := []models.SecurityRuleSet{
		{Name: "owasp-crs"},
	}

	// Test exclusions with targets (partial rule exclusion)
	exclusionsJSON := `[{"rule_id":942100,"target":"ARGS:password"},{"rule_id":941100,"target":"ARGS:content"}]`
	secCfg := &models.SecurityConfig{
		WAFMode:        "block",
		WAFRulesSource: "owasp-crs",
		WAFExclusions:  exclusionsJSON,
	}

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

	directives := h["directives"].(string)
	require.Contains(t, directives, `SecRuleUpdateTargetById 942100 "!ARGS:password"`)
	require.Contains(t, directives, `SecRuleUpdateTargetById 941100 "!ARGS:content"`)
}

// TestBuildWAFHandler_PerHostDisabled verifies returns nil when host.WAFDisabled is true
func TestBuildWAFHandler_PerHostDisabled(t *testing.T) {
	rulesetPaths := map[string]string{
		"owasp-crs": "/etc/caddy/rules/owasp-crs.conf",
	}
	rulesets := []models.SecurityRuleSet{
		{Name: "owasp-crs"},
	}
	secCfg := &models.SecurityConfig{
		WAFMode:        "block",
		WAFRulesSource: "owasp-crs",
	}

	// Host with WAF disabled
	host := &models.ProxyHost{
		UUID:        "test-uuid",
		WAFDisabled: true,
	}

	h, err := buildWAFHandler(host, rulesets, rulesetPaths, secCfg, true)
	require.NoError(t, err)
	require.Nil(t, h, "WAF handler should be nil when host.WAFDisabled is true")

	// Host with WAF enabled (default)
	host2 := &models.ProxyHost{
		UUID:        "test-uuid-2",
		WAFDisabled: false,
	}

	h2, err := buildWAFHandler(host2, rulesets, rulesetPaths, secCfg, true)
	require.NoError(t, err)
	require.NotNil(t, h2, "WAF handler should not be nil when host.WAFDisabled is false")
}

// TestBuildWAFHandler_MonitorMode verifies DetectionOnly when mode is "monitor"
func TestBuildWAFHandler_MonitorMode(t *testing.T) {
	rulesetPaths := map[string]string{
		"owasp-crs": "/etc/caddy/rules/owasp-crs.conf",
	}
	rulesets := []models.SecurityRuleSet{
		{Name: "owasp-crs"},
	}

	// Monitor mode
	secCfg := &models.SecurityConfig{
		WAFMode:        "monitor",
		WAFRulesSource: "owasp-crs",
	}

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

	directives := h["directives"].(string)
	require.Contains(t, directives, "SecRuleEngine DetectionOnly")
	require.NotContains(t, directives, "SecRuleEngine On")

	// Block mode
	secCfg2 := &models.SecurityConfig{
		WAFMode:        "block",
		WAFRulesSource: "owasp-crs",
	}

	h2, err := buildWAFHandler(nil, rulesets, rulesetPaths, secCfg2, true)
	require.NoError(t, err)
	require.NotNil(t, h2)

	directives2 := h2["directives"].(string)
	require.Contains(t, directives2, "SecRuleEngine On")
	require.NotContains(t, directives2, "SecRuleEngine DetectionOnly")
}

// TestBuildWAFHandler_GlobalDisabled verifies handler returns nil when globally disabled
func TestBuildWAFHandler_GlobalDisabled(t *testing.T) {
	rulesetPaths := map[string]string{
		"owasp-crs": "/etc/caddy/rules/owasp-crs.conf",
	}
	rulesets := []models.SecurityRuleSet{
		{Name: "owasp-crs"},
	}

	// WAF disabled via wafEnabled flag
	secCfg := &models.SecurityConfig{
		WAFMode:        "block",
		WAFRulesSource: "owasp-crs",
	}

	h, err := buildWAFHandler(nil, rulesets, rulesetPaths, secCfg, false)
	require.NoError(t, err)
	require.Nil(t, h)

	// WAF disabled via SecurityConfig.WAFMode
	secCfg2 := &models.SecurityConfig{
		WAFMode:        "disabled",
		WAFRulesSource: "owasp-crs",
	}

	h2, err := buildWAFHandler(nil, rulesets, rulesetPaths, secCfg2, true)
	require.NoError(t, err)
	require.Nil(t, h2)
}

// TestBuildWAFHandler_NoRuleset verifies handler returns nil when no ruleset available
// WAF without rules is essentially a no-op, so we return nil.
func TestBuildWAFHandler_NoRuleset(t *testing.T) {
	// Empty rulesets and ruleset paths
	secCfg := &models.SecurityConfig{
		WAFMode: "block",
	}

	h, err := buildWAFHandler(nil, nil, nil, secCfg, true)
	require.NoError(t, err)
	require.Nil(t, h, "WAF handler should be nil when no ruleset is available")
}

// TestParseWAFExclusions verifies exclusion parsing from JSON
func TestParseWAFExclusions(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected []WAFExclusion
	}{
		{
			name:     "empty",
			input:    "",
			expected: nil,
		},
		{
			name:  "single_exclusion",
			input: `[{"rule_id":942100}]`,
			expected: []WAFExclusion{
				{RuleID: 942100},
			},
		},
		{
			name:  "multiple_exclusions",
			input: `[{"rule_id":942100,"description":"SQL Injection"},{"rule_id":941100,"target":"ARGS:password"}]`,
			expected: []WAFExclusion{
				{RuleID: 942100, Description: "SQL Injection"},
				{RuleID: 941100, Target: "ARGS:password"},
			},
		},
		{
			name:     "invalid_json",
			input:    `invalid json`,
			expected: nil,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := parseWAFExclusions(tt.input)
			if tt.expected == nil {
				require.Nil(t, result)
			} else {
				require.Equal(t, len(tt.expected), len(result))
				for i, e := range tt.expected {
					require.Equal(t, e.RuleID, result[i].RuleID)
					require.Equal(t, e.Target, result[i].Target)
					require.Equal(t, e.Description, result[i].Description)
				}
			}
		})
	}
}

// TestGenerateConfig_WithWAFPerHostDisabled verifies per-host WAF toggle in full config generation
func TestGenerateConfig_WithWAFPerHostDisabled(t *testing.T) {
	hosts := []models.ProxyHost{
		{
			UUID:        "uuid-waf-enabled",
			DomainNames: "waf-enabled.example.com",
			ForwardHost: "app1",
			ForwardPort: 8080,
			Enabled:     true,
			WAFDisabled: false,
		},
		{
			UUID:        "uuid-waf-disabled",
			DomainNames: "waf-disabled.example.com",
			ForwardHost: "app2",
			ForwardPort: 8081,
			Enabled:     true,
			WAFDisabled: true,
		},
	}

	rulesetPaths := map[string]string{
		"owasp-crs": "/etc/caddy/rules/owasp-crs.conf",
	}
	rulesets := []models.SecurityRuleSet{
		{Name: "owasp-crs"},
	}
	secCfg := &models.SecurityConfig{
		WAFMode:        "block",
		WAFRulesSource: "owasp-crs",
	}

	config, err := GenerateConfig(hosts, "/tmp/caddy-data", "admin@example.com", "", "", false, false, true, false, false, "", rulesets, rulesetPaths, nil, secCfg)
	require.NoError(t, err)
	require.NotNil(t, config.Apps.HTTP)

	server := config.Apps.HTTP.Servers["charon_server"]
	require.NotNil(t, server)
	require.Len(t, server.Routes, 2)

	// Check waf-enabled host has WAF handler
	var wafEnabledRoute, wafDisabledRoute *Route
	for _, route := range server.Routes {
		if len(route.Match) > 0 && len(route.Match[0].Host) > 0 {
			if route.Match[0].Host[0] == "waf-enabled.example.com" {
				wafEnabledRoute = route
			} else if route.Match[0].Host[0] == "waf-disabled.example.com" {
				wafDisabledRoute = route
			}
		}
	}

	// WAF-enabled route should have WAF handler
	require.NotNil(t, wafEnabledRoute)
	foundWAF := false
	for _, h := range wafEnabledRoute.Handle {
		if h["handler"] == "waf" {
			foundWAF = true
			break
		}
	}
	require.True(t, foundWAF, "WAF handler should be present for waf-enabled host")

	// WAF-disabled route should NOT have WAF handler
	require.NotNil(t, wafDisabledRoute)
	for _, h := range wafDisabledRoute.Handle {
		require.NotEqual(t, "waf", h["handler"], "WAF handler should NOT be present for waf-disabled host")
	}
}
