package config

import (
	"os"
	"path/filepath"
	"testing"
	"time"
)

func TestConfig_Load(t *testing.T) {
	tests := []struct {
		name        string
		configYAML  string
		wantErr     bool
		errContains string
		validate    func(*testing.T, *Config)
	}{
		{
			name: "valid minimal config",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
metrics:
  enabled: false
`,
			wantErr: false,
			validate: func(t *testing.T, cfg *Config) {
				if cfg.Server.Addr != "0.0.0.0" {
					t.Errorf("expected addr 0.0.0.0, got %s", cfg.Server.Addr)
				}
				if cfg.Server.Port != 8080 {
					t.Errorf("expected port 8080, got %d", cfg.Server.Port)
				}
			},
		},
		{
			name: "valid config with routes",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
metrics:
  enabled: true
  port: 8081
  path: /metrics
clusterRoutes:
  - name: pd-api
    match:
      prefix: /pd
    backend:
      scheme: https
      port: 2379
    tls:
      enabled: true
      secretRef:
        name: "db-cluster-client-secret"
    timeout: 30s
`,
			wantErr: false,
			validate: func(t *testing.T, cfg *Config) {
				if len(cfg.ClusterRoutes) != 1 {
					t.Errorf("expected 1 cluster route, got %d", len(cfg.ClusterRoutes))
				}
				if cfg.ClusterRoutes[0].Name != "pd-api" {
					t.Errorf("expected route name pd-api, got %s", cfg.ClusterRoutes[0].Name)
				}
				if cfg.ClusterRoutes[0].Timeout != 30*time.Second {
					t.Errorf("expected timeout 30s, got %v", cfg.ClusterRoutes[0].Timeout)
				}
			},
		},
		{
			name: "missing server addr",
			configYAML: `
server:
  port: 8080
`,
			wantErr:     true,
			errContains: "addr is required",
		},
		{
			name: "invalid server port",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 0
`,
			wantErr:     true,
			errContains: "port must be between",
		},
		{
			name: "metrics enabled without path",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
metrics:
  enabled: true
  port: 8081
`,
			wantErr:     true,
			errContains: "path is required",
		},
		{
			name: "route missing name",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
clusterRoutes:
  - match:
      prefix: /pd
`,
			wantErr:     true,
			errContains: "name is required",
		},
		{
			name: "route tls enabled without secret",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
clusterRoutes:
  - name: test
    match:
      prefix: /test
    tls:
      enabled: true
`,
			wantErr:     true,
			errContains: "secretRef.name is required",
		},
		{
			name:        "invalid yaml",
			configYAML:  `invalid: [yaml`,
			wantErr:     true,
			errContains: "parse config",
		},
		{
			name: "serviceNamePattern and template both set",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
clusterRoutes:
  - name: tibr
    match:
      prefix: /tibr/{name}
    backend:
      serviceNamePattern: "^.*$"
      serviceNameTemplate: "{name}-tibr-headless"
    tls:
      enabled: false
`,
			wantErr:     true,
			errContains: "mutually exclusive",
		},
		{
			name: "path params require serviceNameTemplate",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
clusterRoutes:
  - name: tibr
    match:
      prefix: /tibr/{name}
    backend:
      scheme: https
    tls:
      enabled: false
`,
			wantErr:     true,
			errContains: "serviceNameTemplate is required",
		},
		{
			name: "template must contain params",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
clusterRoutes:
  - name: tibr
    match:
      prefix: /tibr/{name}
    backend:
      serviceNameTemplate: "tibr-headless"
    tls:
      enabled: false
`,
			wantErr:     true,
			errContains: "must contain placeholder {name}",
		},
		{
			name: "duplicate route name in cluster routes",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
clusterRoutes:
  - name: pd-api
    match:
      prefix: /pd
  - name: pd-api
    match:
      prefix: /tikv
`,
			wantErr:     true,
			errContains: "duplicate route name: pd-api",
		},
		{
			name: "duplicate route name across cluster and service routes",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
clusterRoutes:
  - name: shared-name
    match:
      prefix: /pd
serviceRoutes:
  - name: shared-name
    match:
      prefix: /service
`,
			wantErr:     true,
			errContains: "duplicate route name: shared-name",
		},
		{
			name: "duplicate prefix in cluster routes",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
clusterRoutes:
  - name: route1
    match:
      prefix: /pd
  - name: route2
    match:
      prefix: /pd
`,
			wantErr:     true,
			errContains: "duplicate prefix",
		},
		{
			name: "prefix conflict - one is prefix of another",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
clusterRoutes:
  - name: pd-api
    match:
      prefix: /pd
  - name: pd-api-v2
    match:
      prefix: /pd/api
`,
			wantErr:     true,
			errContains: "prefix conflict",
		},
		{
			name: "prefix conflict across cluster and service routes",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
clusterRoutes:
  - name: cluster-route
    match:
      prefix: /api
serviceRoutes:
  - name: service-route
    match:
      prefix: /api/v1
`,
			wantErr:     true,
			errContains: "prefix conflict",
		},
		{
			name: "valid config with non-conflicting prefixes",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
clusterRoutes:
  - name: pd-api
    match:
      prefix: /pd
  - name: tikv-api
    match:
      prefix: /tikv
serviceRoutes:
  - name: prometheus
    match:
      prefix: /prometheus
`,
			wantErr: false,
			validate: func(t *testing.T, cfg *Config) {
				if len(cfg.ClusterRoutes) != 2 {
					t.Errorf("expected 2 cluster routes, got %d", len(cfg.ClusterRoutes))
				}
				if len(cfg.ServiceRoutes) != 1 {
					t.Errorf("expected 1 service route, got %d", len(cfg.ServiceRoutes))
				}
			},
		},
		{
			name: "duplicate path parameter name in prefix",
			configYAML: `
server:
  addr: "0.0.0.0"
  port: 8080
clusterRoutes:
  - name: bad-route
    match:
      prefix: /api/{version}/{version}
    backend:
      serviceNameTemplate: "{version}-svc"
    tls:
      enabled: false
`,
			wantErr:     true,
			errContains: "duplicate path parameter",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create temp config file
			tmpDir := t.TempDir()
			configPath := filepath.Join(tmpDir, "config.yaml")
			if err := os.WriteFile(configPath, []byte(tt.configYAML), 0644); err != nil {
				t.Fatalf("failed to write config file: %v", err)
			}

			manager := NewManager(configPath)
			err := manager.Load()

			if tt.wantErr {
				if err == nil {
					t.Error("expected error, got nil")
				} else if tt.errContains != "" && !contains(err.Error(), tt.errContains) {
					t.Errorf("error %q does not contain %q", err.Error(), tt.errContains)
				}
				return
			}

			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			cfg := manager.Get()
			if cfg == nil {
				t.Fatal("config is nil")
			}

			if tt.validate != nil {
				tt.validate(t, cfg)
			}
		})
	}
}

func TestConfig_FileNotFound(t *testing.T) {
	manager := NewManager("/nonexistent/path/config.yaml")
	err := manager.Load()
	if err == nil {
		t.Error("expected error for nonexistent file")
	}
}

func TestValidateRouteNameUniqueness(t *testing.T) {
	tests := []struct {
		name          string
		clusterRoutes []RouteConfig
		serviceRoutes []RouteConfig
		wantErr       bool
		errContains   string
	}{
		{
			name: "unique names",
			clusterRoutes: []RouteConfig{
				{Name: "route1", Match: MatchConfig{Prefix: "/a"}},
				{Name: "route2", Match: MatchConfig{Prefix: "/b"}},
			},
			serviceRoutes: []RouteConfig{
				{Name: "route3", Match: MatchConfig{Prefix: "/c"}},
			},
			wantErr: false,
		},
		{
			name: "duplicate in cluster routes",
			clusterRoutes: []RouteConfig{
				{Name: "route1", Match: MatchConfig{Prefix: "/a"}},
				{Name: "route1", Match: MatchConfig{Prefix: "/b"}},
			},
			wantErr:     true,
			errContains: "duplicate route name: route1",
		},
		{
			name: "duplicate across route types",
			clusterRoutes: []RouteConfig{
				{Name: "shared", Match: MatchConfig{Prefix: "/a"}},
			},
			serviceRoutes: []RouteConfig{
				{Name: "shared", Match: MatchConfig{Prefix: "/b"}},
			},
			wantErr:     true,
			errContains: "duplicate route name: shared",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateRouteNameUniqueness(tt.clusterRoutes, tt.serviceRoutes)

			if tt.wantErr {
				if err == nil {
					t.Error("expected error, got nil")
				} else if tt.errContains != "" && !contains(err.Error(), tt.errContains) {
					t.Errorf("error %q does not contain %q", err.Error(), tt.errContains)
				}
				return
			}

			if err != nil {
				t.Errorf("unexpected error: %v", err)
			}
		})
	}
}

func TestValidateRoutePrefixConflicts(t *testing.T) {
	tests := []struct {
		name          string
		clusterRoutes []RouteConfig
		serviceRoutes []RouteConfig
		wantErr       bool
		errContains   string
	}{
		{
			name: "no conflicts",
			clusterRoutes: []RouteConfig{
				{Name: "route1", Match: MatchConfig{Prefix: "/pd"}},
				{Name: "route2", Match: MatchConfig{Prefix: "/tikv"}},
			},
			wantErr: false,
		},
		{
			name: "exact duplicate prefix",
			clusterRoutes: []RouteConfig{
				{Name: "route1", Match: MatchConfig{Prefix: "/pd"}},
				{Name: "route2", Match: MatchConfig{Prefix: "/pd"}},
			},
			wantErr:     true,
			errContains: "duplicate prefix",
		},
		{
			name: "prefix overlap - shorter first",
			clusterRoutes: []RouteConfig{
				{Name: "route1", Match: MatchConfig{Prefix: "/pd"}},
				{Name: "route2", Match: MatchConfig{Prefix: "/pd/api"}},
			},
			wantErr:     true,
			errContains: "prefix conflict",
		},
		{
			name: "prefix overlap - longer first",
			clusterRoutes: []RouteConfig{
				{Name: "route1", Match: MatchConfig{Prefix: "/pd/api"}},
				{Name: "route2", Match: MatchConfig{Prefix: "/pd"}},
			},
			wantErr:     true,
			errContains: "prefix conflict",
		},
		{
			name: "similar but non-conflicting prefixes",
			clusterRoutes: []RouteConfig{
				{Name: "route1", Match: MatchConfig{Prefix: "/pd"}},
				{Name: "route2", Match: MatchConfig{Prefix: "/pd2"}},
			},
			wantErr: false,
		},
		{
			name: "conflict across route types",
			clusterRoutes: []RouteConfig{
				{Name: "cluster", Match: MatchConfig{Prefix: "/api"}},
			},
			serviceRoutes: []RouteConfig{
				{Name: "service", Match: MatchConfig{Prefix: "/api/v1"}},
			},
			wantErr:     true,
			errContains: "prefix conflict",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateRoutePrefixConflicts(tt.clusterRoutes, tt.serviceRoutes)

			if tt.wantErr {
				if err == nil {
					t.Error("expected error, got nil")
				} else if tt.errContains != "" && !contains(err.Error(), tt.errContains) {
					t.Errorf("error %q does not contain %q", err.Error(), tt.errContains)
				}
				return
			}

			if err != nil {
				t.Errorf("unexpected error: %v", err)
			}
		})
	}
}

func TestHasPrefixConflict(t *testing.T) {
	tests := []struct {
		name     string
		prefix1  string
		prefix2  string
		expected bool
	}{
		{
			name:     "no conflict - different paths",
			prefix1:  "/pd",
			prefix2:  "/tikv",
			expected: false,
		},
		{
			name:     "conflict - one is prefix of another",
			prefix1:  "/pd",
			prefix2:  "/pd/api",
			expected: true,
		},
		{
			name:     "conflict - reverse order",
			prefix1:  "/pd/api",
			prefix2:  "/pd",
			expected: true,
		},
		{
			name:     "no conflict - similar but not prefix",
			prefix1:  "/pd",
			prefix2:  "/pd2",
			expected: false,
		},
		{
			name:     "no conflict - exact same (handled separately)",
			prefix1:  "/pd",
			prefix2:  "/pd",
			expected: false,
		},
		{
			name:     "conflict - deep nesting",
			prefix1:  "/api/v1/users",
			prefix2:  "/api",
			expected: true,
		},
		{
			name:     "no conflict - without leading slash",
			prefix1:  "pd",
			prefix2:  "tikv",
			expected: false,
		},
		{
			name:     "conflict - without leading slash",
			prefix1:  "pd",
			prefix2:  "pd/api",
			expected: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := hasPrefixConflict(tt.prefix1, tt.prefix2)
			if result != tt.expected {
				t.Errorf("hasPrefixConflict(%q, %q) = %v, want %v",
					tt.prefix1, tt.prefix2, result, tt.expected)
			}
		})
	}
}

func contains(s, substr string) bool {
	return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsHelper(s, substr))
}

func containsHelper(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
