package configrefresh_test

import (
	"context"
	"testing"

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

// TestClassifyNodeRuntimeConcurrencyApplied verifies S15: node runtime
// concurrency is applied live.
func TestClassifyNodeRuntimeConcurrencyApplied(t *testing.T) {
	dir := t.TempDir()

	currentYAML := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - id: "node-1"
    alias: "n1"
    token: "tok-1"
    adapters:
      ollama:
        enabled: true
        base_url: "http://127.0.0.1:11434"
    runtime:
      concurrency: 2
`
	currentPath := writeYAML(t, dir, "current.yaml", currentYAML)

	concurrencyOnlyYAML := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - id: "node-1"
    alias: "n1"
    token: "tok-1"
    adapters:
      ollama:
        enabled: true
        base_url: "http://127.0.0.1:11434"
    runtime:
      concurrency: 4
`
	concurrencyOnlyPath := writeYAML(t, dir, "concurrency.yaml", concurrencyOnlyYAML)

	current := buildNormalizedCurrent(t, currentPath)
	ctx := context.Background()

	// concurrency-only change → applied.
	result, _, err := configrefresh.Evaluate(ctx, current, configrefresh.Request{
		Mode:       configrefresh.ModeDryRun,
		ConfigPath: concurrencyOnlyPath,
		RequestID:  "test-s15-conc",
	})
	if err != nil {
		t.Fatalf("Evaluate concurrency: %v", err)
	}
	if result.Status != configrefresh.StatusApplied {
		t.Fatalf("expected concurrency change status=applied, got %q (summary: %s)", result.Status, result.Summary)
	}
	foundConc := false
	for _, c := range result.Changes {
		if c.Path == `nodes["node-1"].runtime.concurrency` {
			foundConc = true
			if c.Class != configrefresh.StatusApplied {
				t.Errorf("expected concurrency class=applied, got %q", c.Class)
			}
		}
		if c.Class == configrefresh.StatusRestartRequired {
			t.Errorf("unexpected restart_required change for concurrency-only diff: %s", c.Path)
		}
	}
	if !foundConc {
		t.Errorf("concurrency change path not found in: %+v", result.Changes)
	}
}

func TestClassifyModelTokenCounterApplied(t *testing.T) {
	current := &config.EdgeConfig{Models: []config.ModelCatalogEntry{{
		ID: "model-a", TokenCounter: &config.TokenCounterConf{Mode: config.TokenCounterDeterministic},
	}}}
	candidate := &config.EdgeConfig{Models: []config.ModelCatalogEntry{{
		ID: "model-a", TokenCounter: &config.TokenCounterConf{Mode: config.TokenCounterEstimate, Per1kInput: 250},
	}}}
	result := configrefresh.Classify(current, candidate)
	if result.Status != configrefresh.StatusApplied {
		t.Fatalf("status = %q, want applied; changes=%+v", result.Status, result.Changes)
	}
	wantPath := `models["model-a"].token_counter`
	for _, change := range result.Changes {
		if change.Path == wantPath && change.Class == configrefresh.StatusApplied {
			return
		}
	}
	t.Fatalf("token_counter applied path missing: %+v", result.Changes)
}

func TestClassifyModelCatalogProviderMappingApplied(t *testing.T) {
	dir := t.TempDir()
	currentYAML := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - id: "node-1"
    alias: "n1"
    token: "tok-1"
    adapters:
      ollama:
        enabled: true
        base_url: "http://127.0.0.1:11434"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        adapter: "ollama"
        models: ["llama3.1"]
        capacity: 2
      - id: "prov-b"
        type: "ollama"
        category: "local_inference"
        adapter: "ollama"
        models: ["llama3.1"]
        capacity: 2
models:
  - id: "qwen3.6:35b"
    display_name: "Qwen"
    context_window_tokens: 131072
    default_max_tokens: 8192
    min_max_tokens: 8192
    providers:
      prov-a: "llama3.1"
 `
	candidateYAML := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - id: "node-1"
    alias: "n1"
    token: "tok-1"
    adapters:
      ollama:
        enabled: true
        base_url: "http://127.0.0.1:11434"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        adapter: "ollama"
        models: ["llama3.1"]
        capacity: 2
      - id: "prov-b"
        type: "ollama"
        category: "local_inference"
        adapter: "ollama"
        models: ["llama3.1"]
        capacity: 2
models:
  - id: "qwen3.6:35b"
    display_name: "Qwen"
    context_window_tokens: 262144
    default_max_tokens: 32768
    min_max_tokens: 32768
    default_thinking_token_budget: 8192
    providers:
      prov-b: "llama3.1"
 `
	currentPath := writeYAML(t, dir, "current.yaml", currentYAML)
	candidatePath := writeYAML(t, dir, "candidate.yaml", candidateYAML)

	current := buildNormalizedCurrent(t, currentPath)

	result, _, err := configrefresh.Evaluate(context.Background(), current, configrefresh.Request{
		Mode:       configrefresh.ModeDryRun,
		ConfigPath: candidatePath,
		RequestID:  "test-model-catalog",
	})
	if err != nil {
		t.Fatalf("Evaluate: %v", err)
	}
	if result.Status != configrefresh.StatusApplied {
		t.Fatalf("expected status=%q, got %q changes=%+v", configrefresh.StatusApplied, result.Status, result.Changes)
	}
	found := map[string]bool{}
	for _, change := range result.Changes {
		if change.Class == configrefresh.StatusApplied {
			found[change.Path] = true
		}
	}
	for _, path := range []string{
		`models["qwen3.6:35b"].context_window_tokens`,
		`models["qwen3.6:35b"].default_max_tokens`,
		`models["qwen3.6:35b"].min_max_tokens`,
		`models["qwen3.6:35b"].default_thinking_token_budget`,
		`models["qwen3.6:35b"].providers`,
	} {
		if !found[path] {
			t.Fatalf("model catalog change %s not found in %+v", path, result.Changes)
		}
	}
}

func TestClassifyLongContextThresholdApplied(t *testing.T) {
	dir := t.TempDir()
	currentYAML := `
server:
  listen: "0.0.0.0:9090"
long_context_threshold_tokens: 100000
nodes:
  - id: "node-1"
    alias: "n1"
    token: "tok-1"
    adapters:
      ollama:
        enabled: true
        base_url: "http://127.0.0.1:11434"
`
	candidateYAML := `
server:
  listen: "0.0.0.0:9090"
long_context_threshold_tokens: 120000
nodes:
  - id: "node-1"
    alias: "n1"
    token: "tok-1"
    adapters:
      ollama:
        enabled: true
        base_url: "http://127.0.0.1:11434"
`
	currentPath := writeYAML(t, dir, "current.yaml", currentYAML)
	candidatePath := writeYAML(t, dir, "candidate.yaml", candidateYAML)
	current := buildNormalizedCurrent(t, currentPath)

	result, _, err := configrefresh.Evaluate(context.Background(), current, configrefresh.Request{
		Mode:       configrefresh.ModeDryRun,
		ConfigPath: candidatePath,
		RequestID:  "test-long-threshold",
	})
	if err != nil {
		t.Fatalf("Evaluate: %v", err)
	}
	if result.Status != configrefresh.StatusApplied {
		t.Fatalf("expected status=%q, got %q changes=%+v", configrefresh.StatusApplied, result.Status, result.Changes)
	}
	found := false
	for _, change := range result.Changes {
		if change.Path == "long_context_threshold_tokens" {
			found = true
			if change.Class != configrefresh.StatusApplied {
				t.Errorf("threshold class: got %q, want applied", change.Class)
			}
			if change.Previous != "100000" || change.Next != "120000" {
				t.Errorf("threshold diff: got %s→%s", change.Previous, change.Next)
			}
		}
		if change.Class == configrefresh.StatusRestartRequired {
			t.Errorf("unexpected restart_required threshold change: %s", change.Path)
		}
	}
	if !found {
		t.Fatalf("long_context_threshold_tokens change not found in %+v", result.Changes)
	}
}
