package bootstrap

import (
	"context"
	"testing"

	"iop/apps/edge/internal/configrefresh"
	"iop/packages/go/config"
)

func TestRuntimeRefreshReplacesExecutionPresetGeneration(t *testing.T) {
	initialPreset := config.ExecutionPreset{
		ID: "preset-alpha",
		Selector: config.ExecutionModelBinding{
			Model: "gpt-4o",
			Options: map[string]any{
				"temp":         0.7,
				"nested_map":   map[string]string{"key1": "val1"},
				"nested_slice": []string{"opt1", "opt2"},
			},
		},
		AllowedModes: []string{config.ModeDirect, config.ModeLight},
		Routes: map[string]config.ExecutionRoute{
			config.ModeDirect: {Stages: []config.ExecutionRouteStage{}},
			config.ModeLight: {
				Stages: []config.ExecutionRouteStage{
					{Role: "local", Model: "gpt-4o", Options: map[string]any{"stage_map": map[string]int{"a": 10}}},
					{Role: "review", Model: "gpt-4o", Options: map[string]any{"stage_slice": []int{1, 2}}},
				},
			},
		},
		WorkspaceTools: []config.ExecutionWorkspaceToolAlternative{
			{
				Name: "default",
				Operations: map[string]config.ExecutionWorkspaceOperation{
					"read": {
						ToolName:      "file_read",
						SchemaMatcher: map[string]any{"sm_map": map[string]bool{"read_ok": true}},
						ArgumentMap:   map[string]any{"arg_slice": []string{"path"}},
						ResultMatcher: map[string]any{"res_map": map[string]any{"status": 200}},
					},
					"write": {
						ToolName:       "file_write",
						CreatesParents: true,
						SchemaMatcher:  map[string]any{"sm": "w"},
						ArgumentMap:    map[string]any{"arg": "w"},
						ResultMatcher:  map[string]any{"res": "w"},
					},
					"delete": {
						ToolName:      "file_delete",
						SchemaMatcher: map[string]any{"sm": "d"},
						ArgumentMap:   map[string]any{"arg": "d"},
						ResultMatcher: map[string]any{"res": "d"},
					},
				},
			},
		},
	}

	cfg := newTestConfig()
	cfg.ExecutionPresets = []config.ExecutionPreset{initialPreset}

	rt, err := NewRuntime(cfg)
	if err != nil {
		t.Fatalf("NewRuntime: %v", err)
	}

	// Immutability test: mutate input slice and nested map after setting
	cfg.ExecutionPresets[0].ID = "mutated-preset"
	cfg.ExecutionPresets[0].Selector.Options["temp"] = 1.9
	cfg.ExecutionPresets[0].Selector.Options["nested_map"].(map[string]string)["key1"] = "mutated_val"
	cfg.ExecutionPresets[0].Selector.Options["nested_slice"].([]string)[0] = "mutated_opt"
	cfg.ExecutionPresets[0].Routes[config.ModeLight].Stages[0].Options["stage_map"].(map[string]int)["a"] = 999
	cfg.ExecutionPresets[0].WorkspaceTools[0].Operations["read"].SchemaMatcher["sm_map"].(map[string]bool)["read_ok"] = false
	cfg.ExecutionPresets[0].WorkspaceTools[0].Operations["read"].ArgumentMap["arg_slice"].([]string)[0] = "mutated_path"

	preRefreshSnap := rt.Input.OpenAI.ExecutionPresetsSnapshot()
	if len(preRefreshSnap) != 1 || preRefreshSnap[0].ID != "preset-alpha" {
		t.Fatalf("expected preRefreshSnap to retain preset-alpha, got: %+v", preRefreshSnap)
	}
	if preRefreshSnap[0].Selector.Options["temp"] != 0.7 {
		t.Fatalf("expected preRefreshSnap options temp=0.7, got %v", preRefreshSnap[0].Selector.Options["temp"])
	}
	if val := preRefreshSnap[0].Selector.Options["nested_map"].(map[string]string)["key1"]; val != "val1" {
		t.Fatalf("expected nested_map key1=val1, got %v", val)
	}
	if val := preRefreshSnap[0].Selector.Options["nested_slice"].([]string)[0]; val != "opt1" {
		t.Fatalf("expected nested_slice[0]=opt1, got %v", val)
	}
	if val := preRefreshSnap[0].Routes[config.ModeLight].Stages[0].Options["stage_map"].(map[string]int)["a"]; val != 10 {
		t.Fatalf("expected stage_map a=10, got %v", val)
	}
	if val := preRefreshSnap[0].WorkspaceTools[0].Operations["read"].SchemaMatcher["sm_map"].(map[string]bool)["read_ok"]; !val {
		t.Fatalf("expected sm_map read_ok=true, got %v", val)
	}
	if val := preRefreshSnap[0].WorkspaceTools[0].Operations["read"].ArgumentMap["arg_slice"].([]string)[0]; val != "path" {
		t.Fatalf("expected arg_slice[0]=path, got %v", val)
	}

	// Mutate preRefreshSnap read output and verify internal server snapshot is unchanged
	preRefreshSnap[0].Selector.Options["temp"] = 99.0
	preRefreshSnap[0].Selector.Options["nested_map"].(map[string]string)["key1"] = "snap_mutated"
	preRefreshSnap[0].Selector.Options["nested_slice"].([]string)[0] = "snap_mutated"
	preRefreshSnap[0].Routes[config.ModeLight].Stages[0].Options["stage_map"].(map[string]int)["a"] = 888
	preRefreshSnap[0].WorkspaceTools[0].Operations["read"].SchemaMatcher["sm_map"].(map[string]bool)["read_ok"] = false

	preRefreshSnap2, _ := rt.Input.OpenAI.ExecutionPreset("preset-alpha")
	if preRefreshSnap2.Selector.Options["temp"] != 0.7 {
		t.Fatalf("expected internal snapshot options temp=0.7, got %v", preRefreshSnap2.Selector.Options["temp"])
	}
	if val := preRefreshSnap2.Selector.Options["nested_map"].(map[string]string)["key1"]; val != "val1" {
		t.Fatalf("expected internal snapshot nested_map key1=val1, got %v", val)
	}
	if val := preRefreshSnap2.Selector.Options["nested_slice"].([]string)[0]; val != "opt1" {
		t.Fatalf("expected internal snapshot nested_slice[0]=opt1, got %v", val)
	}
	if val := preRefreshSnap2.Routes[config.ModeLight].Stages[0].Options["stage_map"].(map[string]int)["a"]; val != 10 {
		t.Fatalf("expected internal snapshot stage_map a=10, got %v", val)
	}
	if val := preRefreshSnap2.WorkspaceTools[0].Operations["read"].SchemaMatcher["sm_map"].(map[string]bool)["read_ok"]; !val {
		t.Fatalf("expected internal snapshot sm_map read_ok=true, got %v", val)
	}

	// Perform refresh to replace generation
	candidatePreset1 := config.ExecutionPreset{
		ID:           "preset-alpha",
		Selector:     config.ExecutionModelBinding{Model: "gpt-4o-mini", Options: map[string]any{"temp": 0.2}},
		AllowedModes: []string{config.ModeDirect},
		Routes: map[string]config.ExecutionRoute{
			config.ModeDirect: {Stages: []config.ExecutionRouteStage{}},
		},
	}
	candidatePreset2 := config.ExecutionPreset{
		ID:           "preset-beta",
		Selector:     config.ExecutionModelBinding{Model: "claude-3-5-sonnet"},
		AllowedModes: []string{config.ModeDirect},
		Routes: map[string]config.ExecutionRoute{
			config.ModeDirect: {Stages: []config.ExecutionRouteStage{}},
		},
	}

	candidateCfg := newTestConfig()
	candidateCfg.ExecutionPresets = []config.ExecutionPreset{candidatePreset1, candidatePreset2}

	// Manually invoke applyMutableConfig
	changes := []configrefresh.Change{
		{Path: `execution_presets["preset-alpha"].selector`, Class: configrefresh.StatusApplied},
		{Path: `execution_presets["preset-beta"]`, Class: configrefresh.StatusApplied},
	}

	_, err = rt.applyMutableConfig(context.Background(), candidateCfg, changes, "req-preset-refresh")
	if err != nil {
		t.Fatalf("applyMutableConfig: %v", err)
	}

	// Assert retained preRefreshSnap2 is still unchanged
	if preRefreshSnap2.Selector.Model != "gpt-4o" {
		t.Fatalf("retained pre-refresh snapshot mutated! expected gpt-4o, got %s", preRefreshSnap2.Selector.Model)
	}
	if val := preRefreshSnap2.Selector.Options["nested_map"].(map[string]string)["key1"]; val != "val1" {
		t.Fatalf("retained pre-refresh snapshot mutated! expected key1=val1, got %v", val)
	}

	// Assert post-refresh read sees new generation
	postRefreshSnap := rt.Input.OpenAI.ExecutionPresetsSnapshot()
	if len(postRefreshSnap) != 2 {
		t.Fatalf("expected 2 presets post refresh, got %d", len(postRefreshSnap))
	}

	pAlpha, okAlpha := rt.Input.OpenAI.ExecutionPreset("preset-alpha")
	if !okAlpha || pAlpha.Selector.Model != "gpt-4o-mini" {
		t.Fatalf("post-refresh preset-alpha model: got %s, want gpt-4o-mini", pAlpha.Selector.Model)
	}

	pBeta, okBeta := rt.Input.OpenAI.ExecutionPreset("preset-beta")
	if !okBeta || pBeta.Selector.Model != "claude-3-5-sonnet" {
		t.Fatalf("post-refresh preset-beta model: got %s, want claude-3-5-sonnet", pBeta.Selector.Model)
	}
}
