package config

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

// writeTmp writes a file under a temp path and returns it.
func writeTmp(t *testing.T, path, body string) string {
	t.Helper()
	if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
		t.Fatal(err)
	}
	return path
}

// miniModels loads a minimal valid models.yaml with a single `fake` model.
func miniModels(t *testing.T) *Models {
	t.Helper()
	dir := t.TempDir()
	body := "prices_checked: " + time.Now().UTC().Format("2006-01-02") + `
default_model: fake
providers:
  p: { kind: openai, base_url: http://x }
models:
  fake: { provider: p, price: { input_per_m: 1, output_per_m: 2 } }
`
	m, err := LoadModels(writeTmp(t, filepath.Join(dir, "models.yaml"), body))
	if err != nil {
		t.Fatalf("miniModels: %v", err)
	}
	return m
}

// prompt_pack_test.go pins the pair-keyed prompt seam (D39 слой 2, L4-prompts-not-per-pair-zh-baked):
// a stage resolves its prompt from a pair-keyed pack by the book's LangPair(), FAIL-LOUD on a missing
// pair; a legacy single `prompt` stays pair-agnostic; prod zh→ru resolves the same file (SHA-neutral).

func TestPromptPathForResolvesPair(t *testing.T) {
	st := Stage{Name: "edit", Prompts: map[string]string{"zh-ru": "/x/editor.md", "ja-ru": "/y/editor.md"}}
	got, err := st.PromptPathFor("zh-ru")
	if err != nil || got != "/x/editor.md" {
		t.Fatalf("PromptPathFor(zh-ru) = %q, %v; want /x/editor.md, nil", got, err)
	}
}

func TestPromptPathForFailsLoudOnMissingPair(t *testing.T) {
	st := Stage{Name: "edit", Prompts: map[string]string{"zh-ru": "/x/editor.md"}}
	_, err := st.PromptPathFor("ja-ru")
	if err == nil {
		t.Fatal("PromptPathFor(ja-ru) must FAIL LOUD when the pack has no ja-ru (the latent ja-runs-zh-prompt bug)")
	}
	// The error must name the missing pair AND list what the pack has (operator-actionable).
	if !strings.Contains(err.Error(), "ja-ru") || !strings.Contains(err.Error(), "zh-ru") {
		t.Errorf("fail-loud error must name the missing pair and the pack's pairs, got: %v", err)
	}
}

func TestPromptPathForLegacySingleIsPairAgnostic(t *testing.T) {
	st := Stage{Name: "draft", Prompt: "/x/translator.md"}
	for _, pair := range []string{"zh-ru", "ja-ru", "en-ru"} {
		got, err := st.PromptPathFor(pair)
		if err != nil || got != "/x/translator.md" {
			t.Errorf("legacy single prompt must resolve for %q: got %q, %v", pair, got, err)
		}
	}
}

func TestLoadPipelineRejectsAmbiguousPromptForms(t *testing.T) {
	dir := t.TempDir()
	writeTmp(t, filepath.Join(dir, "translator.md"), "sys\n---USER---\n{{text}}")
	models := miniModels(t)

	// Both `prompt` and `prompts` set → rejected.
	both := writeTmp(t, filepath.Join(dir, "both.yaml"), `
core: C1
stages:
  - name: draft
    role: translator
    model: fake
    prompt: translator.md
    prompts: { zh-ru: translator.md }
    prompt_version: v1
`)
	if _, err := LoadPipeline(both, models); err == nil || !strings.Contains(err.Error(), "not both") {
		t.Errorf("both prompt+prompts must be rejected, got: %v", err)
	}

	// Neither set → rejected.
	neither := writeTmp(t, filepath.Join(dir, "neither.yaml"), `
core: C1
stages:
  - name: draft
    role: translator
    model: fake
    prompt_version: v1
`)
	if _, err := LoadPipeline(neither, models); err == nil || !strings.Contains(err.Error(), "prompt is required") {
		t.Errorf("neither prompt nor prompts must be rejected, got: %v", err)
	}
}

// TestBoevoyPipelineC1ResolvesZhRu proves the migrated prod config is behaviour-neutral for zh→ru:
// the draft/edit stages resolve the same translator.md/editor.md the legacy path did (same file →
// same PromptSHA256), and a NON-zh pair fails loud.
func TestBoevoyPipelineC1ResolvesZhRu(t *testing.T) {
	models, err := LoadModels(filepath.Join("..", "..", "configs", "models.yaml"))
	if err != nil {
		t.Fatalf("load models: %v", err)
	}
	p, err := LoadPipeline(filepath.Join("..", "..", "configs", "pipeline-c1.yaml"), models)
	if err != nil {
		t.Fatalf("load pipeline-c1: %v", err)
	}
	for _, st := range p.Stages {
		zh, err := st.PromptPathFor("zh-ru")
		if err != nil {
			t.Errorf("stage %q must resolve zh-ru: %v", st.Name, err)
		}
		if !strings.HasSuffix(zh, ".md") {
			t.Errorf("stage %q zh-ru prompt should be a .md template, got %q", st.Name, zh)
		}
		// A book of a pair the pack does not carry must fail loud (closes the ja-runs-zh-prompt bug).
		if _, err := st.PromptPathFor("ja-ru"); err == nil {
			t.Errorf("stage %q must fail loud for ja-ru (pack is zh-ru only)", st.Name)
		}
	}
	// The draft stage must resolve to the shared translator.md (the zh→ru pack content is unchanged).
	for _, st := range p.Stages {
		if st.Role == "translator" {
			zh, _ := st.PromptPathFor("zh-ru")
			if !strings.HasSuffix(zh, filepath.Join("prompts", "translator.md")) {
				t.Errorf("draft zh-ru must be prompts/translator.md, got %q", zh)
			}
		}
	}
}
