package config

import (
	"strings"
	"testing"
)

func TestCheckRunnableRejectsPhase2Mechanics(t *testing.T) {
	cases := []struct {
		name string
		p    Pipeline
		want string // substring the error must mention
	}{
		{"c1 ok", Pipeline{Core: "C1", Stages: []Stage{{Name: "draft", Role: "translator"}}}, ""},
		{"c0 ok", Pipeline{Core: "C0", Stages: []Stage{{Name: "draft", Role: "translator"}}}, ""},
		{"c2 rejected", Pipeline{Core: "C2", Stages: []Stage{{Name: "draft", Role: "translator"}}}, "C2"},
		{"fanout rejected", Pipeline{Core: "C1", Fanout: Fanout{Candidates: 3}, Stages: []Stage{{Name: "draft", Role: "translator"}}}, "fanout"},
		{"judge rejected", Pipeline{Core: "C1", Stages: []Stage{{Name: "select", Role: "judge"}}}, "judge"},
		// The coverage gate is EXECUTABLE from Веха 2.5 — CheckRunnable no longer
		// rejects it (threshold sanity is LoadPipeline's job, not a mechanics gate).
		{"enabled gate runnable", Pipeline{Core: "C1", Gates: Gates{Coverage: CoverageGate{Enabled: true}}, Stages: []Stage{{Name: "draft", Role: "translator"}}}, ""},
	}
	for _, c := range cases {
		err := c.p.CheckRunnable()
		if c.want == "" {
			if err != nil {
				t.Errorf("%s: expected runnable, got %v", c.name, err)
			}
			continue
		}
		if err == nil || !strings.Contains(err.Error(), c.want) {
			t.Errorf("%s: expected error mentioning %q, got %v", c.name, c.want, err)
		}
	}
}

// TestCheckAdultChannel covers the D20.2-Q3 load-time consistency lint: an adult:true book must
// route through a channel:adult stage, else the 18+ text runs on the SFW channel. The reverse
// (channel:adult stage in an adult:false book) is deliberately NOT gated (Adult stays neutral).
// Reverting CheckAdultChannel makes the "adult no adult-stage" case stop erroring.
func TestCheckAdultChannel(t *testing.T) {
	sfw := Pipeline{Stages: []Stage{{Name: "draft", Role: "translator"}, {Name: "edit", Role: "editor"}}}
	withAdult := Pipeline{Stages: []Stage{{Name: "draft", Role: "translator", Channel: "adult"}, {Name: "edit", Role: "editor"}}}
	cases := []struct {
		name    string
		p       Pipeline
		adult   bool
		wantErr bool
	}{
		{"sfw book, sfw pipeline", sfw, false, false},
		{"adult book, adult stage present", withAdult, true, false},
		{"adult book, NO adult stage → loud", sfw, true, true},
		{"sfw book, adult stage present (reverse not gated)", withAdult, false, false},
	}
	for _, c := range cases {
		err := c.p.CheckAdultChannel(c.adult)
		if c.wantErr && (err == nil || !strings.Contains(err.Error(), "channel:adult")) {
			t.Errorf("%s: want an error mentioning channel:adult, got %v", c.name, err)
		}
		if !c.wantErr && err != nil {
			t.Errorf("%s: want ok, got %v", c.name, err)
		}
	}
}

func TestCheckKeysOnlyUsedNonLocalModels(t *testing.T) {
	m := &Models{
		Providers: map[string]Provider{
			"cloud": {Kind: "openai", BaseURL: "http://x", APIKeyEnv: "TEST_CLOUD_KEY"},
			"local": {Kind: "local", BaseURL: "http://127.0.0.1", Model: "q"},
		},
		Models: map[string]Model{
			"cloud-model":  {Provider: "cloud"},
			"local-model":  {Provider: "local"},
			"unused-cloud": {Provider: "cloud"},
		},
	}
	// local-only pipeline не должен падать на облачном ключе.
	localPipe := &Pipeline{Stages: []Stage{{Model: "local-model"}}}
	if err := m.CheckKeys(localPipe); err != nil {
		t.Fatalf("local-only pipeline must not require cloud keys: %v", err)
	}
	// Пайплайн с облачной моделью без ключа — fail-fast.
	t.Setenv("TEST_CLOUD_KEY", "")
	cloudPipe := &Pipeline{Stages: []Stage{{Model: "cloud-model"}}}
	if err := m.CheckKeys(cloudPipe); err == nil || !strings.Contains(err.Error(), "TEST_CLOUD_KEY") {
		t.Fatalf("missing cloud key must fail-fast mentioning the env var, got %v", err)
	}
	// Ключ задан — проходит.
	t.Setenv("TEST_CLOUD_KEY", "sk-xxx")
	if err := m.CheckKeys(cloudPipe); err != nil {
		t.Fatalf("present key must pass: %v", err)
	}
	// Эскалационные ключи НЕ требуются, пока эскалация выключена (budget_usd=0,
	// opt-in). Включённый coverage-гейт НЕ делает эскалацию достижимой — гейт
	// моделей не зовёт (self-review: иначе включение гейта блокирует валидный
	// deepseek+glm прогон из-за отсутствующего Kimi-ключа заглушки-цепочки).
	t.Setenv("TEST_CLOUD_KEY", "")
	escOffPipe := &Pipeline{
		Stages: []Stage{{Model: "local-model"}},
		Escal:  Escalation{Chains: map[string][]string{"default": {"cloud-model"}}},
		Gates:  Gates{Coverage: CoverageGate{Enabled: true}}, // gate ON, budget 0
	}
	if err := m.CheckKeys(escOffPipe); err != nil {
		t.Fatalf("enabling the coverage gate must NOT require escalation keys at budget 0: %v", err)
	}
	// При budget_usd>0 эскалация достижима → ключ цепочки обязателен.
	escOnPipe := &Pipeline{
		Stages: []Stage{{Model: "local-model"}},
		Escal:  Escalation{Chains: map[string][]string{"default": {"cloud-model"}}, BudgetUSD: 1.0},
	}
	if err := m.CheckKeys(escOnPipe); err == nil {
		t.Fatal("escalation chain model without a key must fail-fast when budget_usd>0")
	}
}
