package configrefresh

import (
	"testing"

	"iop/packages/go/config"
)

// makeTestConfig builds a minimal EdgeConfig with the given workspaces for
// classification testing. It is not a valid full config for LoadEdge; it is
// used only as in-memory current/candidate pairs for Classify.
func makeTestConfig(workspaces []config.WorkspaceDefinition) *config.EdgeConfig {
	return &config.EdgeConfig{
		Edge: config.EdgeInfo{ID: "edge-test", Name: "edge-test"},
		Server: config.EdgeServerConf{Listen: "0.0.0.0:9090"},
		Bootstrap: config.EdgeBootstrapConf{
			Listen:   "0.0.0.0:18080",
			ArtifactDir: "artifacts",
		},
		OpenAI: config.EdgeOpenAIConf{Listen: "0.0.0.0:18081", Adapter: "ollama"},
		Logging: config.LoggingConf{Level: "info"},
		Metrics: config.MetricsConf{Port: 19092},
		Refresh: config.EdgeRefreshConf{Enabled: false, Listen: "127.0.0.1:19093"},
		LongContextThresholdTokens: 100000,
		Nodes: []config.NodeDefinition{
			{
				ID:    "node-ws-test",
				Alias: "ws-test-node",
				Token: "token-ws-test",
				Providers: []config.NodeProviderConf{
					{
						ID:         "prov-a",
						Type:       "ollama",
						Category:   config.CategoryLocalInference,
						Models:     []string{"model-a"},
						Capacity:   2,
					},
				},
				Workspaces: workspaces,
			},
		},
	}
}

func TestClassifyWorkspaceRootChangeRequiresRestart(t *testing.T) {
	current := makeTestConfig([]config.WorkspaceDefinition{
		{Ref: "ws-root-change", Platform: "darwin", Root: "/Users/operator/projects/old", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}},
	})
	candidate := makeTestConfig([]config.WorkspaceDefinition{
		{Ref: "ws-root-change", Platform: "darwin", Root: "/Users/operator/projects/new", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}},
	})

	result := Classify(current, candidate)
	if result.Status != StatusRestartRequired {
		t.Fatalf("expected restart_required for root change, got %s", result.Status)
	}
	found := false
	for _, c := range result.Changes {
		if c.Class == StatusRestartRequired && containsStr(c.Path, "workspaces") {
			found = true
		}
	}
	if !found {
		t.Fatalf("expected restart_required change on nodes[].workspaces, got %v", result.Changes)
	}
}

func TestClassifyWorkspaceCapabilityChangeRequiresRestart(t *testing.T) {
	current := makeTestConfig([]config.WorkspaceDefinition{
		{Ref: "ws-cap-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}},
	})
	candidate := makeTestConfig([]config.WorkspaceDefinition{
		{Ref: "ws-cap-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead, config.WorkspaceOpWrite}},
	})

	result := Classify(current, candidate)
	if result.Status != StatusRestartRequired {
		t.Fatalf("expected restart_required for capability change, got %s", result.Status)
	}
}

func TestClassifyWorkspaceAdditionRequiresRestart(t *testing.T) {
	current := makeTestConfig(nil)
	candidate := makeTestConfig([]config.WorkspaceDefinition{
		{Ref: "ws-new", Platform: "darwin", Root: "/Users/operator/projects/new", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}},
	})

	result := Classify(current, candidate)
	if result.Status != StatusRestartRequired {
		t.Fatalf("expected restart_required for workspace addition, got %s", result.Status)
	}
}

func TestClassifyWorkspaceRemovalRequiresRestart(t *testing.T) {
	current := makeTestConfig([]config.WorkspaceDefinition{
		{Ref: "ws-to-remove", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}},
	})
	candidate := makeTestConfig(nil)

	result := Classify(current, candidate)
	if result.Status != StatusRestartRequired {
		t.Fatalf("expected restart_required for workspace removal, got %s", result.Status)
	}
}

func TestClassifyWorkspaceNoChangeIsApplied(t *testing.T) {
	ws := []config.WorkspaceDefinition{
		{Ref: "ws-no-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}},
	}
	current := makeTestConfig(ws)
	candidate := makeTestConfig(ws)

	result := Classify(current, candidate)
	if result.Status != StatusApplied {
		t.Fatalf("expected applied for no workspace change, got %s", result.Status)
	}
}

func TestClassifyWorkspaceEnvironmentAllowlistChangeRequiresRestart(t *testing.T) {
	current := makeTestConfig([]config.WorkspaceDefinition{
		{Ref: "ws-env-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}, EnvironmentAllowlist: []string{"PATH"}},
	})
	candidate := makeTestConfig([]config.WorkspaceDefinition{
		{Ref: "ws-env-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}, EnvironmentAllowlist: []string{"PATH", "HOME"}},
	})

	result := Classify(current, candidate)
	if result.Status != StatusRestartRequired {
		t.Fatalf("expected restart_required for environment allowlist change, got %s", result.Status)
	}
}

func TestClassifyWorkspaceLimitsChangeRequiresRestart(t *testing.T) {
	current := makeTestConfig([]config.WorkspaceDefinition{
		{Ref: "ws-limits-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}, MaxReadBytes: 1024},
	})
	candidate := makeTestConfig([]config.WorkspaceDefinition{
		{Ref: "ws-limits-change", Platform: "darwin", Root: "/Users/operator/projects/test", Operations: []config.WorkspaceOperation{config.WorkspaceOpRead}, MaxReadBytes: 2048},
	})

	result := Classify(current, candidate)
	if result.Status != StatusRestartRequired {
		t.Fatalf("expected restart_required for limits change, got %s", result.Status)
	}
}

// containsStr reports whether s contains substr.
func containsStr(s, substr string) bool {
	for i := 0; i+len(substr) <= len(s); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
