package config_test

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

	"iop/packages/go/config"
)

func TestStreamEvidenceGate_DirectStructZeroValueDefaults(t *testing.T) {
	var cfg config.EdgeOpenAIConf

	if got := cfg.StreamEvidenceGate.EffectiveMaxRequestFaultRecovery(); got != 3 {
		t.Errorf("EffectiveMaxRequestFaultRecovery() = %d, want 3", got)
	}
	if got := cfg.StreamEvidenceGate.EffectiveMaxStrategyFaultRecovery(); got != 3 {
		t.Errorf("EffectiveMaxStrategyFaultRecovery() = %d, want 3", got)
	}
	if got := cfg.StreamEvidenceGate.EffectiveMaxIngressSnapshotBytes(); got != 16777216 {
		t.Errorf("EffectiveMaxIngressSnapshotBytes() = %d, want 16777216 (16 MiB)", got)
	}
	if err := cfg.StreamEvidenceGate.Validate(); err != nil {
		t.Errorf("Validate() unexpectedly failed on zero-value struct: %v", err)
	}
}

func TestStreamEvidenceGate_YAMLOmittedDefaults(t *testing.T) {
	yamlContent := `
openai:
  enabled: true
  provider_id: "test-provider"
`
	tmpDir := t.TempDir()
	tmpFile := filepath.Join(tmpDir, "edge.yaml")
	if err := os.WriteFile(tmpFile, []byte(yamlContent), 0644); err != nil {
		t.Fatalf("failed to write temp yaml: %v", err)
	}

	cfg, err := config.LoadEdge(tmpFile)
	if err != nil {
		t.Fatalf("LoadEdge failed: %v", err)
	}

	gate := cfg.OpenAI.StreamEvidenceGate
	if got := gate.EffectiveMaxRequestFaultRecovery(); got != 3 {
		t.Errorf("EffectiveMaxRequestFaultRecovery() = %d, want 3", got)
	}
	if got := gate.EffectiveMaxStrategyFaultRecovery(); got != 3 {
		t.Errorf("EffectiveMaxStrategyFaultRecovery() = %d, want 3", got)
	}
	if got := gate.EffectiveMaxIngressSnapshotBytes(); got != 16777216 {
		t.Errorf("EffectiveMaxIngressSnapshotBytes() = %d, want 16777216", got)
	}
}

func TestStreamEvidenceGate_EnabledField_Load(t *testing.T) {
	tests := []struct {
		name        string
		gateYAML    string
		wantEnabled bool
	}{
		{
			name:        "omitted defaults to disabled",
			gateYAML:    "",
			wantEnabled: false,
		},
		{
			name: "explicit true enables the request runtime",
			gateYAML: `
  stream_evidence_gate:
    enabled: true`,
			wantEnabled: true,
		},
		{
			name: "explicit false stays disabled",
			gateYAML: `
  stream_evidence_gate:
    enabled: false`,
			wantEnabled: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			yamlContent := "openai:\n  enabled: true\n  provider_id: \"test-provider\"" + tt.gateYAML + "\n"
			tmpDir := t.TempDir()
			tmpFile := filepath.Join(tmpDir, "edge.yaml")
			if err := os.WriteFile(tmpFile, []byte(yamlContent), 0644); err != nil {
				t.Fatalf("failed to write temp yaml: %v", err)
			}
			cfg, err := config.LoadEdge(tmpFile)
			if err != nil {
				t.Fatalf("LoadEdge failed: %v", err)
			}
			if got := cfg.OpenAI.StreamEvidenceGate.Enabled; got != tt.wantEnabled {
				t.Errorf("StreamEvidenceGate.Enabled = %v, want %v", got, tt.wantEnabled)
			}
		})
	}
}

func TestStreamEvidenceGate_RequestFaultRecovery_Table(t *testing.T) {
	tests := []struct {
		name         string
		totalYAML    string
		expectErr    bool
		wantTotal    int
		wantStrategy int
	}{
		{
			name:         "explicit 0 disables fault recovery",
			totalYAML:    "max_request_fault_recovery: 0",
			expectErr:    false,
			wantTotal:    0,
			wantStrategy: 0,
		},
		{
			name:         "explicit 1",
			totalYAML:    "max_request_fault_recovery: 1",
			expectErr:    false,
			wantTotal:    1,
			wantStrategy: 1,
		},
		{
			name:         "explicit 3",
			totalYAML:    "max_request_fault_recovery: 3",
			expectErr:    false,
			wantTotal:    3,
			wantStrategy: 3,
		},
		{
			name:      "explicit 4 rejection",
			totalYAML: "max_request_fault_recovery: 4",
			expectErr: true,
		},
		{
			name:      "negative total rejection",
			totalYAML: "max_request_fault_recovery: -1",
			expectErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			yamlContent := `
openai:
  enabled: true
  provider_id: "test-provider"
  stream_evidence_gate:
    ` + tt.totalYAML + `
`
			tmpDir := t.TempDir()
			tmpFile := filepath.Join(tmpDir, "edge.yaml")
			if err := os.WriteFile(tmpFile, []byte(yamlContent), 0644); err != nil {
				t.Fatalf("failed to write temp yaml: %v", err)
			}

			cfg, err := config.LoadEdge(tmpFile)
			if tt.expectErr {
				if err == nil {
					t.Errorf("LoadEdge succeeded unexpectedly, wanted error for %s", tt.totalYAML)
				}
				return
			}
			if err != nil {
				t.Fatalf("LoadEdge failed: %v", err)
			}

			gate := cfg.OpenAI.StreamEvidenceGate
			if got := gate.EffectiveMaxRequestFaultRecovery(); got != tt.wantTotal {
				t.Errorf("EffectiveMaxRequestFaultRecovery() = %d, want %d", got, tt.wantTotal)
			}
			if got := gate.EffectiveMaxStrategyFaultRecovery(); got != tt.wantStrategy {
				t.Errorf("EffectiveMaxStrategyFaultRecovery() = %d, want %d", got, tt.wantStrategy)
			}
		})
	}
}

func TestStreamEvidenceGate_StrategyFaultRecovery_Table(t *testing.T) {
	tests := []struct {
		name         string
		gateYAML     string
		expectErr    bool
		wantTotal    int
		wantStrategy int
	}{
		{
			name: "total 3, strategy 0",
			gateYAML: `
    max_request_fault_recovery: 3
    max_strategy_fault_recovery: 0`,
			expectErr:    false,
			wantTotal:    3,
			wantStrategy: 0,
		},
		{
			name: "total 3, strategy 2",
			gateYAML: `
    max_request_fault_recovery: 3
    max_strategy_fault_recovery: 2`,
			expectErr:    false,
			wantTotal:    3,
			wantStrategy: 2,
		},
		{
			name: "total 3, strategy 3",
			gateYAML: `
    max_request_fault_recovery: 3
    max_strategy_fault_recovery: 3`,
			expectErr:    false,
			wantTotal:    3,
			wantStrategy: 3,
		},
		{
			name: "total 3, strategy 4 rejection (strategy > total)",
			gateYAML: `
    max_request_fault_recovery: 3
    max_strategy_fault_recovery: 4`,
			expectErr: true,
		},
		{
			name: "total 1, strategy 2 rejection (strategy > total)",
			gateYAML: `
    max_request_fault_recovery: 1
    max_strategy_fault_recovery: 2`,
			expectErr: true,
		},
		{
			name: "total 0, strategy 1 rejection (strategy > total)",
			gateYAML: `
    max_request_fault_recovery: 0
    max_strategy_fault_recovery: 1`,
			expectErr: true,
		},
		{
			name: "strategy negative rejection",
			gateYAML: `
    max_request_fault_recovery: 3
    max_strategy_fault_recovery: -1`,
			expectErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			yamlContent := `
openai:
  enabled: true
  provider_id: "test-provider"
  stream_evidence_gate:` + tt.gateYAML + `
`
			tmpDir := t.TempDir()
			tmpFile := filepath.Join(tmpDir, "edge.yaml")
			if err := os.WriteFile(tmpFile, []byte(yamlContent), 0644); err != nil {
				t.Fatalf("failed to write temp yaml: %v", err)
			}

			cfg, err := config.LoadEdge(tmpFile)
			if tt.expectErr {
				if err == nil {
					t.Errorf("LoadEdge succeeded unexpectedly, wanted error for %s", tt.gateYAML)
				}
				return
			}
			if err != nil {
				t.Fatalf("LoadEdge failed: %v", err)
			}

			gate := cfg.OpenAI.StreamEvidenceGate
			if got := gate.EffectiveMaxRequestFaultRecovery(); got != tt.wantTotal {
				t.Errorf("EffectiveMaxRequestFaultRecovery() = %d, want %d", got, tt.wantTotal)
			}
			if got := gate.EffectiveMaxStrategyFaultRecovery(); got != tt.wantStrategy {
				t.Errorf("EffectiveMaxStrategyFaultRecovery() = %d, want %d", got, tt.wantStrategy)
			}
		})
	}
}

// TestStreamGateFilterPolicy_Defaults verifies the per-filter effective
// defaults (enabled=true, enforcement=blocking, capability=output.<kind>,
// hold=500, timeout=5000) that the runtime relies on when fields are omitted.
func TestStreamGateFilterPolicy_Defaults(t *testing.T) {
	f := config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard}
	if !f.EffectiveEnabled() {
		t.Errorf("EffectiveEnabled() = false, want true by default")
	}
	if got := f.EffectiveEnforcement(); got != config.StreamGateFilterEnforcementBlocking {
		t.Errorf("EffectiveEnforcement() = %q, want blocking", got)
	}
	if got := f.EffectiveCapability(); got != "output.repeat_guard" {
		t.Errorf("EffectiveCapability() = %q, want output.repeat_guard", got)
	}
	if got := f.EffectiveHoldEvidenceRunes(); got != config.DefaultStreamGateFilterHoldEvidenceRunes {
		t.Errorf("EffectiveHoldEvidenceRunes() = %d, want %d", got, config.DefaultStreamGateFilterHoldEvidenceRunes)
	}
	if got := f.EffectiveTimeoutMS(); got != config.DefaultStreamGateFilterTimeoutMS {
		t.Errorf("EffectiveTimeoutMS() = %d, want %d", got, config.DefaultStreamGateFilterTimeoutMS)
	}
	if err := f.Validate(); err != nil {
		t.Errorf("Validate() unexpectedly failed on defaulted filter: %v", err)
	}
}

// TestStreamGateFilterPolicy_ValidateTable covers the config validation contract
// for the semantic output filters: known kinds, enforcement values, selector
// types/keys, and numeric bounds (S01/S02 config).
func TestStreamGateFilterPolicy_ValidateTable(t *testing.T) {
	boolPtr := func(b bool) *bool { return &b }
	tests := []struct {
		name      string
		filter    config.StreamGateFilterPolicyConf
		expectErr bool
	}{
		{name: "valid repeat guard", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard}},
		{name: "valid schema gate observe_only", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterSchemaGate, Enforcement: config.StreamGateFilterEnforcementObserveOnly}},
		{name: "valid provider error with selectors", filter: config.StreamGateFilterPolicyConf{
			Filter:   config.StreamGateFilterProviderError,
			Priority: 20,
			Selectors: []config.StreamGateFilterSelectorConf{
				{Type: config.StreamGateFilterSelectorProvider, Key: "prov-a", Enabled: boolPtr(false)},
				{Type: config.StreamGateFilterSelectorModel, Key: "ornith:35b", Enforcement: config.StreamGateFilterEnforcementObserveOnly},
			},
		}},
		{name: "unknown kind", filter: config.StreamGateFilterPolicyConf{Filter: "malformed_guard"}, expectErr: true},
		{name: "unknown enforcement", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, Enforcement: "warn"}, expectErr: true},
		{name: "hold runes over max", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, HoldEvidenceRunes: config.MaxStreamGateFilterHoldEvidenceRunes + 1}, expectErr: true},
		{name: "negative hold runes", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, HoldEvidenceRunes: -1}, expectErr: true},
		{name: "timeout over max", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, TimeoutMS: config.MaxStreamGateFilterTimeoutMS + 1}, expectErr: true},
		{name: "negative priority", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, Priority: -1}, expectErr: true},
		{name: "unknown selector type", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, Selectors: []config.StreamGateFilterSelectorConf{{Type: "region", Key: "kr"}}}, expectErr: true},
		{name: "empty selector key", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, Selectors: []config.StreamGateFilterSelectorConf{{Type: config.StreamGateFilterSelectorModel, Key: "  "}}}, expectErr: true},
		{name: "unknown selector enforcement", filter: config.StreamGateFilterPolicyConf{Filter: config.StreamGateFilterRepeatGuard, Selectors: []config.StreamGateFilterSelectorConf{{Type: config.StreamGateFilterSelectorModel, Key: "m", Enforcement: "warn"}}}, expectErr: true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.filter.Validate()
			if tt.expectErr && err == nil {
				t.Fatalf("Validate() succeeded, want error")
			}
			if !tt.expectErr && err != nil {
				t.Fatalf("Validate() failed: %v", err)
			}
		})
	}
}

// TestStreamGateFilterPolicy_LoadAndDuplicateRejection loads a full filter policy
// from YAML and confirms a duplicate filter kind is rejected at load time.
func TestStreamGateFilterPolicy_LoadAndDuplicateRejection(t *testing.T) {
	valid := `
openai:
  enabled: true
  provider_id: "test-provider"
  stream_evidence_gate:
    enabled: true
    filters:
      - filter: repeat_guard
        enforcement: observe_only
        capability: output.repeat_guard
      - filter: provider_error
        priority: 20
        selectors:
          - type: provider
            key: prov-a
            enabled: false
`
	tmpDir := t.TempDir()
	tmpFile := filepath.Join(tmpDir, "edge.yaml")
	if err := os.WriteFile(tmpFile, []byte(valid), 0644); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	cfg, err := config.LoadEdge(tmpFile)
	if err != nil {
		t.Fatalf("LoadEdge valid filters failed: %v", err)
	}
	filters := cfg.OpenAI.StreamEvidenceGate.Filters
	if len(filters) != 2 {
		t.Fatalf("loaded %d filters, want 2", len(filters))
	}
	if filters[0].Filter != config.StreamGateFilterRepeatGuard || filters[0].EffectiveEnforcement() != config.StreamGateFilterEnforcementObserveOnly {
		t.Errorf("filter[0] = %+v", filters[0])
	}
	if len(filters[1].Selectors) != 1 || filters[1].Selectors[0].Type != config.StreamGateFilterSelectorProvider {
		t.Errorf("filter[1] selectors = %+v", filters[1].Selectors)
	}

	dup := `
openai:
  enabled: true
  provider_id: "test-provider"
  stream_evidence_gate:
    enabled: true
    filters:
      - filter: repeat_guard
      - filter: repeat_guard
`
	dupFile := filepath.Join(tmpDir, "dup.yaml")
	if err := os.WriteFile(dupFile, []byte(dup), 0644); err != nil {
		t.Fatalf("write dup yaml: %v", err)
	}
	if _, err := config.LoadEdge(dupFile); err == nil {
		t.Fatalf("LoadEdge with duplicate filter kind succeeded, want error")
	}
}

func TestStreamEvidenceGate_BlockingRepeatGuardRequiresRecoveryBudget(t *testing.T) {
	boolPtr := func(value bool) *bool { return &value }
	zero, one := 0, 1
	tests := []struct {
		name      string
		gate      config.StreamEvidenceGateConf
		expectErr bool
	}{
		{
			name: "blocking base with zero budget",
			gate: config.StreamEvidenceGateConf{
				Enabled:                 true,
				MaxRequestFaultRecovery: &zero, MaxStrategyFaultRecovery: &zero,
				Filters: []config.StreamGateFilterPolicyConf{{Filter: config.StreamGateFilterRepeatGuard}},
			},
			expectErr: true,
		},
		{
			name: "blocking selector with zero budget",
			gate: config.StreamEvidenceGateConf{
				Enabled:                 true,
				MaxRequestFaultRecovery: &zero, MaxStrategyFaultRecovery: &zero,
				Filters: []config.StreamGateFilterPolicyConf{{
					Filter: config.StreamGateFilterRepeatGuard, Enabled: boolPtr(false),
					Selectors: []config.StreamGateFilterSelectorConf{{
						Type: config.StreamGateFilterSelectorModelGroup, Key: "ornith:35b", Enabled: boolPtr(true),
					}},
				}},
			},
			expectErr: true,
		},
		{
			name: "blocking selector with one recovery",
			gate: config.StreamEvidenceGateConf{
				Enabled:                 true,
				MaxRequestFaultRecovery: &one, MaxStrategyFaultRecovery: &one,
				Filters: []config.StreamGateFilterPolicyConf{{
					Filter: config.StreamGateFilterRepeatGuard, Enabled: boolPtr(false),
					Selectors: []config.StreamGateFilterSelectorConf{{
						Type: config.StreamGateFilterSelectorModelGroup, Key: "ornith:35b", Enabled: boolPtr(true),
					}},
				}},
			},
		},
		{
			name: "observe only with zero budget",
			gate: config.StreamEvidenceGateConf{
				Enabled:                 true,
				MaxRequestFaultRecovery: &zero, MaxStrategyFaultRecovery: &zero,
				Filters: []config.StreamGateFilterPolicyConf{{
					Filter: config.StreamGateFilterRepeatGuard, Enforcement: config.StreamGateFilterEnforcementObserveOnly,
				}},
			},
		},
		{
			name: "blocking selector enforcement with zero budget",
			gate: config.StreamEvidenceGateConf{
				Enabled:                 true,
				MaxRequestFaultRecovery: &zero, MaxStrategyFaultRecovery: &zero,
				Filters: []config.StreamGateFilterPolicyConf{{
					Filter: config.StreamGateFilterRepeatGuard, Enforcement: config.StreamGateFilterEnforcementObserveOnly,
					Selectors: []config.StreamGateFilterSelectorConf{{
						Type: config.StreamGateFilterSelectorModelGroup, Key: "ornith:35b", Enforcement: config.StreamGateFilterEnforcementBlocking,
					}},
				}},
			},
			expectErr: true,
		},
		{
			name: "disabled with zero budget",
			gate: config.StreamEvidenceGateConf{
				Enabled:                 true,
				MaxRequestFaultRecovery: &zero, MaxStrategyFaultRecovery: &zero,
				Filters: []config.StreamGateFilterPolicyConf{{
					Filter: config.StreamGateFilterRepeatGuard, Enabled: boolPtr(false),
				}},
			},
		},
		{
			name: "disabled gate with blocking filter and zero budget",
			gate: config.StreamEvidenceGateConf{
				Enabled:                 false,
				MaxRequestFaultRecovery: &zero, MaxStrategyFaultRecovery: &zero,
				Filters: []config.StreamGateFilterPolicyConf{{Filter: config.StreamGateFilterRepeatGuard}},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.gate.Validate()
			if tt.expectErr && err == nil {
				t.Fatal("Validate() succeeded, want recovery-budget error")
			}
			if !tt.expectErr && err != nil {
				t.Fatalf("Validate() failed: %v", err)
			}
		})
	}
}

func TestStreamEvidenceGate_IngressSnapshotBytes_TableFixture(t *testing.T) {
	maxAllowed := 16 * 1024 * 1024 // 16777216

	tests := []struct {
		name        string
		ingressYAML string
		expectErr   bool
		wantBytes   int
	}{
		{
			name:        "ingress limit - 1 (16777215)",
			ingressYAML: "max_ingress_snapshot_bytes: 16777215",
			expectErr:   false,
			wantBytes:   16777215,
		},
		{
			name:        "ingress limit (16777216)",
			ingressYAML: "max_ingress_snapshot_bytes: 16777216",
			expectErr:   false,
			wantBytes:   maxAllowed,
		},
		{
			name:        "ingress limit + 1 (16777217) rejection",
			ingressYAML: "max_ingress_snapshot_bytes: 16777217",
			expectErr:   true,
		},
		{
			name:        "ingress 1 byte boundary",
			ingressYAML: "max_ingress_snapshot_bytes: 1",
			expectErr:   false,
			wantBytes:   1,
		},
		{
			name:        "ingress negative rejection",
			ingressYAML: "max_ingress_snapshot_bytes: -1",
			expectErr:   true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			yamlContent := `
openai:
  enabled: true
  provider_id: "test-provider"
  stream_evidence_gate:
    ` + tt.ingressYAML + `
`
			tmpDir := t.TempDir()
			tmpFile := filepath.Join(tmpDir, "edge.yaml")
			if err := os.WriteFile(tmpFile, []byte(yamlContent), 0644); err != nil {
				t.Fatalf("failed to write temp yaml: %v", err)
			}

			cfg, err := config.LoadEdge(tmpFile)
			if tt.expectErr {
				if err == nil {
					t.Errorf("LoadEdge succeeded unexpectedly, wanted error for %s", tt.ingressYAML)
				}
				return
			}
			if err != nil {
				t.Fatalf("LoadEdge failed: %v", err)
			}

			gate := cfg.OpenAI.StreamEvidenceGate
			if got := gate.EffectiveMaxIngressSnapshotBytes(); got != tt.wantBytes {
				t.Errorf("EffectiveMaxIngressSnapshotBytes() = %d, want %d", got, tt.wantBytes)
			}
		})
	}
}

func TestStreamGateFilterPolicy_Environment(t *testing.T) {
	var defaults config.StreamEvidenceGateConf
	if got := defaults.EffectiveEnvironment(); got != config.StreamGateEnvironmentDev {
		t.Fatalf("default environment=%q, want dev", got)
	}
	for _, environment := range []string{config.StreamGateEnvironmentDev, config.StreamGateEnvironmentDevCorp} {
		cfg := config.StreamEvidenceGateConf{Environment: environment}
		if err := cfg.Validate(); err != nil {
			t.Errorf("Validate(%q): %v", environment, err)
		}
	}
	if err := (config.StreamEvidenceGateConf{Environment: "production"}).Validate(); err == nil {
		t.Fatal("Validate(production) succeeded, want closed environment error")
	}
}
