package pipeline

import (
	"context"
	"crypto/sha256"
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"textmachine/backend/internal/llm"
)

// This is a request-capture probe, not a model-quality test. It executes the
// actual book runner and current production prompt files with an in-memory
// provider. Every response is synthetic and no network client is called.
type contextAuditClient struct{ requests []llm.LLMRequest }

func (c *contextAuditClient) Complete(_ context.Context, r llm.LLMRequest) (*llm.LLMResponse, error) {
	c.requests = append(c.requests, r)
	body, _ := json.Marshal(r.Messages)
	output := "Наконец согласился."
	if strings.Contains(string(body), "女孩") {
		output = "Девушка одна сидела в комнате и обдумывала просьбу."
	}
	if strings.Contains(string(body), "男孩") {
		output = "Юноша один сидел в комнате и обдумывал просьбу."
	}
	return &llm.LLMResponse{Text: output, Model: r.Model, FinishReason: llm.FinishStop,
		Usage: llm.Usage{PromptTokens: 100, CompletionTokens: 20}}, nil
}

func TestAuditContextAcrossEditUnits(t *testing.T) {
	current := "终于答应了。"
	prefixes := []string{"女孩独自坐在房里，反复考虑那个请求。", "男孩独自坐在房里，反复考虑那个请求。"}
	var captures [2][]llm.LLMRequest
	for variant, prefix := range prefixes {
		bookPath := setupProjectOpts(t, "http://127.0.0.1:1", projectOpts{
			source: prefix + "\f" + current, banknote: true, waveWorkers: 1})
		projectDir := filepath.Dir(bookPath)
		bookBytes, err := os.ReadFile(bookPath)
		if err != nil {
			t.Fatal(err)
		}
		writeFile(t, bookPath, strings.Replace(string(bookBytes), "source_lang: ja", "source_lang: zh", 1))
		for _, pair := range [][2]string{{"translator-banknote.md", "translator.md"}, {"editor.md", "editor.md"}} {
			data, err := os.ReadFile(filepath.Join("..", "..", "prompts", "zh-ru", pair[0]))
			if err != nil {
				t.Fatal(err)
			}
			writeFile(t, filepath.Join(projectDir, "prompts", pair[1]), string(data))
		}
		r := newRunner(t, bookPath)
		chunks, err := r.bookChunks()
		if err != nil {
			r.Close()
			t.Fatal(err)
		}
		units := buildEditUnits(chunks)
		if len(units) != 2 || len(units[0].Members) != 1 || len(units[1].Members) != 1 ||
			units[0].Chapter == units[1].Chapter || units[1].sourceText() != current {
			r.Close()
			t.Fatalf("fixture did not cross an edit-unit boundary: chunks=%+v units=%+v", chunks, units)
		}
		fake := &contextAuditClient{}
		r.clients["fake-model"] = fake
		result, err := r.TranslateBook(context.Background())
		r.Close()
		if err != nil {
			t.Fatal(err)
		}
		if len(fake.requests) != 4 || result.Flagged != 0 {
			t.Fatalf("want 2 draft+2 edit calls, clean result; calls=%d result=%+v", len(fake.requests), result)
		}
		captures[variant] = fake.requests
		t.Logf("variant=%d: 2 chapters / 2 draft chunks / 2 edit units / %d in-memory provider calls; previous=%s", variant, len(fake.requests), prefix)
	}
	var currentByVariant [2][]llm.LLMRequest
	var priorByVariant [2][]llm.LLMRequest
	for variant, reqs := range captures {
		for _, req := range reqs {
			b, _ := json.Marshal(req.Messages)
			if strings.Contains(string(b), current) {
				currentByVariant[variant] = append(currentByVariant[variant], req)
			} else {
				priorByVariant[variant] = append(priorByVariant[variant], req)
			}
		}
		if len(currentByVariant[variant]) != 2 || len(priorByVariant[variant]) != 2 {
			t.Fatal("missing draft/editor captures")
		}
	}
	for i, role := range []string{"draft", "editor"} {
		a, _ := json.Marshal(currentByVariant[0][i])
		b, _ := json.Marshal(currentByVariant[1][i])
		if string(a) != string(b) {
			t.Fatalf("current %s request DID change with preceding context: A=%s B=%s", role, a, b)
		}
		pa, _ := json.Marshal(priorByVariant[0][i])
		pb, _ := json.Marshal(priorByVariant[1][i])
		if string(pa) == string(pb) {
			t.Fatalf("prior %s requests should differ", role)
		}
		t.Logf("current %s request is byte-identical: bytes=%d sha256=%x; prior request differs", role, len(a), sha256.Sum256(a))
	}
}
