package caddy

import (
	"encoding/json"
	"fmt"
	"os"
	"testing"

	"github.com/Wikid82/charon/backend/internal/models"
	"github.com/stretchr/testify/require"
)

func TestGenerateConfig_CatchAllFrontend(t *testing.T) {
	cfg, err := GenerateConfig([]models.ProxyHost{}, "/tmp/caddy-data", "", "/frontend/dist", "", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)
	server := cfg.Apps.HTTP.Servers["charon_server"]
	require.NotNil(t, server)
	require.Len(t, server.Routes, 1)
	r := server.Routes[0]
	// Expect first handler is rewrite to unknown.html
	require.Equal(t, "rewrite", r.Handle[0]["handler"])
}

func TestGenerateConfig_AdvancedInvalidJSON(t *testing.T) {
	hosts := []models.ProxyHost{
		{
			UUID:           "adv1",
			DomainNames:    "adv.example.com",
			ForwardHost:    "app",
			ForwardPort:    8080,
			Enabled:        true,
			AdvancedConfig: "{invalid-json",
		},
	}

	cfg, err := GenerateConfig(hosts, "/tmp/caddy-data", "", "", "", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)
	server := cfg.Apps.HTTP.Servers["charon_server"]
	require.NotNil(t, server)
	// Main route should still have ReverseProxy as last handler
	require.Len(t, server.Routes, 1)
	route := server.Routes[0]
	last := route.Handle[len(route.Handle)-1]
	require.Equal(t, "reverse_proxy", last["handler"])
}

func TestGenerateConfig_AdvancedArrayHandler(t *testing.T) {
	array := []map[string]interface{}{{
		"handler": "headers",
		"response": map[string]interface{}{
			"set": map[string][]string{"X-Test": {"1"}},
		},
	}}
	raw, _ := json.Marshal(array)

	hosts := []models.ProxyHost{
		{
			UUID:           "adv2",
			DomainNames:    "arr.example.com",
			ForwardHost:    "app",
			ForwardPort:    8080,
			Enabled:        true,
			AdvancedConfig: string(raw),
		},
	}

	cfg, err := GenerateConfig(hosts, "/tmp/caddy-data", "", "", "", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)
	server := cfg.Apps.HTTP.Servers["charon_server"]
	require.NotNil(t, server)
	route := server.Routes[0]
	// First handler should be our headers handler
	first := route.Handle[0]
	require.Equal(t, "headers", first["handler"])
}

func TestGenerateConfig_LowercaseDomains(t *testing.T) {
	hosts := []models.ProxyHost{
		{UUID: "d1", DomainNames: "UPPER.EXAMPLE.COM", ForwardHost: "a", ForwardPort: 80, Enabled: true},
	}
	cfg, err := GenerateConfig(hosts, "/tmp/caddy-data", "", "", "", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)
	route := cfg.Apps.HTTP.Servers["charon_server"].Routes[0]
	// Debug prints removed
	require.Equal(t, []string{"upper.example.com"}, route.Match[0].Host)
}

func TestGenerateConfig_AdvancedObjectHandler(t *testing.T) {
	host := models.ProxyHost{
		UUID:           "advobj",
		DomainNames:    "obj.example.com",
		ForwardHost:    "app",
		ForwardPort:    8080,
		Enabled:        true,
		AdvancedConfig: `{"handler":"headers","response":{"set":{"X-Obj":["1"]}}}`,
	}
	cfg, err := GenerateConfig([]models.ProxyHost{host}, "/tmp/caddy-data", "", "", "", false, false, false, false, true, "", nil, nil, nil, nil)
	require.NoError(t, err)
	route := cfg.Apps.HTTP.Servers["charon_server"].Routes[0]
	// First handler should be headers
	first := route.Handle[0]
	require.Equal(t, "headers", first["handler"])
}

func TestGenerateConfig_AdvancedHeadersStringToArray(t *testing.T) {
	host := models.ProxyHost{
		UUID:           "advheaders",
		DomainNames:    "hdr.example.com",
		ForwardHost:    "app",
		ForwardPort:    8080,
		Enabled:        true,
		AdvancedConfig: `{"handler":"headers","request":{"set":{"Upgrade":"websocket"}},"response":{"set":{"X-Obj":"1"}}}`,
	}
	cfg, err := GenerateConfig([]models.ProxyHost{host}, "/tmp/caddy-data", "", "", "", false, false, false, false, true, "", nil, nil, nil, nil)
	require.NoError(t, err)
	route := cfg.Apps.HTTP.Servers["charon_server"].Routes[0]
	// Debug prints removed
	first := route.Handle[0]
	require.Equal(t, "headers", first["handler"])

	// request.set.Upgrade should be an array
	if req, ok := first["request"].(map[string]interface{}); ok {
		if set, ok := req["set"].(map[string]interface{}); ok {
			switch val := set["Upgrade"].(type) {
			case []string:
				require.Equal(t, []string{"websocket"}, val)
			case []interface{}:
				var out []string
				for _, v := range val {
					out = append(out, fmt.Sprintf("%v", v))
				}
				require.Equal(t, []string{"websocket"}, out)
			default:
				t.Fatalf("Upgrade header not normalized to array: %#v", set["Upgrade"])
			}
		} else {
			t.Fatalf("request.set not found in handler: %#v", first["request"])
		}
	} else {
		t.Fatalf("request not found in handler: %#v", first)
	}

	// response.set.X-Obj should be an array
	if resp, ok := first["response"].(map[string]interface{}); ok {
		if set, ok := resp["set"].(map[string]interface{}); ok {
			switch val := set["X-Obj"].(type) {
			case []string:
				require.Equal(t, []string{"1"}, val)
			case []interface{}:
				var out []string
				for _, v := range val {
					out = append(out, fmt.Sprintf("%v", v))
				}
				require.Equal(t, []string{"1"}, out)
			default:
				t.Fatalf("X-Obj header not normalized to array: %#v", set["X-Obj"])
			}
		} else {
			t.Fatalf("response.set not found in handler: %#v", first["response"])
		}
	} else {
		t.Fatalf("response not found in handler: %#v", first)
	}
}

func TestGenerateConfig_ACLWhitelistIncluded(t *testing.T) {
	// Create a host with a whitelist ACL
	ipRules := `[{"cidr":"192.168.1.0/24"}]`
	acl := models.AccessList{ID: 100, Name: "WL", Enabled: true, Type: "whitelist", IPRules: ipRules}
	host := models.ProxyHost{UUID: "hasacl", DomainNames: "acl.example.com", Enabled: true, ForwardHost: "app", ForwardPort: 8080, AccessListID: &acl.ID, AccessList: &acl}
	// Sanity check: buildACLHandler should return a subroute handler for this ACL
	aclH, err := buildACLHandler(&acl, "")
	require.NoError(t, err)
	require.NotNil(t, aclH)
	cfg, err := GenerateConfig([]models.ProxyHost{host}, "/tmp/caddy-data", "", "", "", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)
	route := cfg.Apps.HTTP.Servers["charon_server"].Routes[0]
	// Accept either a subroute (ACL) or reverse_proxy as first handler
	first := route.Handle[0]
	if first["handler"] != "subroute" {
		require.Equal(t, "reverse_proxy", first["handler"])
	}
}

func TestGenerateConfig_SkipsEmptyDomainEntries(t *testing.T) {
	hosts := []models.ProxyHost{{UUID: "u1", DomainNames: ", test.example.com", ForwardHost: "a", ForwardPort: 80, Enabled: true}}
	cfg, err := GenerateConfig(hosts, "/tmp/caddy-data", "", "", "", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)
	route := cfg.Apps.HTTP.Servers["charon_server"].Routes[0]
	require.Equal(t, []string{"test.example.com"}, route.Match[0].Host)
}

func TestGenerateConfig_AdvancedNoHandlerKey(t *testing.T) {
	host := models.ProxyHost{UUID: "adv3", DomainNames: "nohandler.example.com", ForwardHost: "app", ForwardPort: 8080, Enabled: true, AdvancedConfig: `{"foo":"bar"}`}
	cfg, err := GenerateConfig([]models.ProxyHost{host}, "/tmp/caddy-data", "", "", "", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)
	route := cfg.Apps.HTTP.Servers["charon_server"].Routes[0]
	// No headers handler appended; last handler is reverse_proxy
	last := route.Handle[len(route.Handle)-1]
	require.Equal(t, "reverse_proxy", last["handler"])
}

func TestGenerateConfig_AdvancedUnexpectedJSONStructure(t *testing.T) {
	host := models.ProxyHost{UUID: "adv4", DomainNames: "struct.example.com", ForwardHost: "app", ForwardPort: 8080, Enabled: true, AdvancedConfig: `42`}
	cfg, err := GenerateConfig([]models.ProxyHost{host}, "/tmp/caddy-data", "", "", "", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)
	route := cfg.Apps.HTTP.Servers["charon_server"].Routes[0]
	// Expect main reverse proxy handler exists but no appended advanced handler
	last := route.Handle[len(route.Handle)-1]
	require.Equal(t, "reverse_proxy", last["handler"])
}

// Test buildACLHandler returning nil when an unknown type is supplied but IPRules present
func TestBuildACLHandler_UnknownIPTypeReturnsNil(t *testing.T) {
	acl := &models.AccessList{Type: "custom", IPRules: `[{"cidr":"10.0.0.0/8"}]`}
	h, err := buildACLHandler(acl, "")
	require.NoError(t, err)
	require.Nil(t, h)
}

func TestGenerateConfig_SecurityPipeline_Order(t *testing.T) {
	// Create host with ACL and HSTS/BlockExploits
	ipRules := `[ { "cidr": "192.168.1.0/24" } ]`
	acl := models.AccessList{ID: 200, Name: "WL", Enabled: true, Type: "whitelist", IPRules: ipRules}
	host := models.ProxyHost{UUID: "pipeline1", DomainNames: "pipe.example.com", Enabled: true, ForwardHost: "app", ForwardPort: 8080, AccessListID: &acl.ID, AccessList: &acl, HSTSEnabled: true, BlockExploits: true}

	// Provide rulesets and paths so WAF handler is created with directives
	rulesets := []models.SecurityRuleSet{{Name: "owasp-crs"}}
	rulesetPaths := map[string]string{"owasp-crs": "/tmp/owasp.conf"}
	// Set rate limit values so rate_limit handler is included (uses caddy-ratelimit format)
	secCfg := &models.SecurityConfig{CrowdSecMode: "local", RateLimitRequests: 100, RateLimitWindowSec: 60}
	cfg, err := GenerateConfig([]models.ProxyHost{host}, "/tmp/caddy-data", "", "", "", false, true, true, true, true, "", rulesets, rulesetPaths, nil, secCfg)
	require.NoError(t, err)
	route := cfg.Apps.HTTP.Servers["charon_server"].Routes[0]

	// Extract handler names
	names := []string{}
	for _, h := range route.Handle {
		if hn, ok := h["handler"].(string); ok {
			names = append(names, hn)
		}
	}

	// Expected pipeline: crowdsec -> waf -> rate_limit -> subroute (acl) -> headers -> vars (BlockExploits) -> reverse_proxy
	require.GreaterOrEqual(t, len(names), 4)
	require.Equal(t, "crowdsec", names[0])
	require.Equal(t, "waf", names[1])
	require.Equal(t, "rate_limit", names[2])
	// ACL is subroute
	require.Equal(t, "subroute", names[3])
}

func TestGenerateConfig_SecurityPipeline_OmitWhenDisabled(t *testing.T) {
	host := models.ProxyHost{UUID: "pipe2", DomainNames: "pipe2.example.com", Enabled: true, ForwardHost: "app", ForwardPort: 8080}
	cfg, err := GenerateConfig([]models.ProxyHost{host}, "/tmp/caddy-data", "", "", "", false, false, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)
	route := cfg.Apps.HTTP.Servers["charon_server"].Routes[0]

	// Extract handler names
	names := []string{}
	for _, h := range route.Handle {
		if hn, ok := h["handler"].(string); ok {
			names = append(names, hn)
		}
	}

	// Should not include the security pipeline placeholders
	for _, n := range names {
		require.NotEqual(t, "crowdsec", n)
		require.NotEqual(t, "coraza", n)
		require.NotEqual(t, "rate_limit", n)
		require.NotEqual(t, "subroute", n)
	}
}

// TestGetAccessLogPath tests the log path selection logic
func TestGetAccessLogPath(t *testing.T) {
	// Save and restore env vars
	origEnv := os.Getenv("CHARON_ENV")
	defer os.Setenv("CHARON_ENV", origEnv)

	t.Run("CrowdSecEnabled_UsesStandardPath", func(t *testing.T) {
		os.Setenv("CHARON_ENV", "development")
		path := getAccessLogPath("/data/caddy/data", true)
		require.Equal(t, "/var/log/caddy/access.log", path)
	})

	t.Run("Production_UsesStandardPath", func(t *testing.T) {
		os.Setenv("CHARON_ENV", "production")
		path := getAccessLogPath("/data/caddy/data", false)
		require.Equal(t, "/var/log/caddy/access.log", path)
	})

	t.Run("Development_UsesRelativePath", func(t *testing.T) {
		os.Setenv("CHARON_ENV", "development")
		path := getAccessLogPath("/data/caddy/data", false)
		// Only in development without CrowdSec should it use relative path
		// Note: This test may fail if /.dockerenv exists (e.g., running in CI container)
		if _, err := os.Stat("/.dockerenv"); err != nil {
			// Not in Docker, should use relative path
			expected := "/data/logs/access.log"
			require.Equal(t, expected, path)
		} else {
			// In Docker, always uses standard path
			require.Equal(t, "/var/log/caddy/access.log", path)
		}
	})

	t.Run("NoEnv_CrowdSecEnabled_UsesStandardPath", func(t *testing.T) {
		os.Unsetenv("CHARON_ENV")
		path := getAccessLogPath("/tmp/caddy-data", true)
		require.Equal(t, "/var/log/caddy/access.log", path)
	})
}

// TestGenerateConfig_LoggingConfigured verifies logging is configured in GenerateConfig output
func TestGenerateConfig_LoggingConfigured(t *testing.T) {
	cfg, err := GenerateConfig([]models.ProxyHost{}, "/data/caddy/data", "", "", "", false, true, false, false, false, "", nil, nil, nil, nil)
	require.NoError(t, err)

	// Logging should be configured
	require.NotNil(t, cfg.Logging)
	require.NotNil(t, cfg.Logging.Logs)
	require.Contains(t, cfg.Logging.Logs, "access")

	accessLog := cfg.Logging.Logs["access"]
	require.NotNil(t, accessLog)
	require.Equal(t, "INFO", accessLog.Level)

	// Writer should be configured for file output
	require.NotNil(t, accessLog.Writer)
	require.Equal(t, "file", accessLog.Writer.Output)
	// When CrowdSec is enabled, the path should be /var/log/caddy/access.log
	require.Equal(t, "/var/log/caddy/access.log", accessLog.Writer.Filename)

	// Encoder should be JSON
	require.NotNil(t, accessLog.Encoder)
	require.Equal(t, "json", accessLog.Encoder.Format)

	// Should include access log directive
	require.Contains(t, accessLog.Include, "http.log.access.access_log")
}
