package docx

import (
	"strings"
	"testing"
)

// docBody wraps a <w:body> inner string into a full document.xml.
func docBody(inner string) string {
	return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
		`<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">` +
		`<w:body>` + inner + `</w:body></w:document>`
}

func TestImportForAuthoring_PreviewIsRunAddressable(t *testing.T) {
	body := docBody(
		`<w:p><w:r><w:t>Az. 4c O 12/23</w:t></w:r></w:p>` +
			`<w:p><w:r><w:t>Klägerin</w:t></w:r><w:r><w:t xml:space="preserve"> GmbH</w:t></w:r></w:p>`)
	view, err := ImportForAuthoring(minimalMergeDOCX(t, body))
	if err != nil {
		t.Fatalf("ImportForAuthoring: %v", err)
	}
	// Three <w:t> → three run spans, indexed 0,1,2 in document order.
	for i, want := range []string{`data-run="0"`, `data-run="1"`, `data-run="2"`} {
		if !strings.Contains(view.PreviewHTML, want) {
			t.Errorf("preview missing %s (run %d); html=%s", want, i, view.PreviewHTML)
		}
	}
	if !strings.Contains(view.PreviewHTML, "Az. 4c O 12/23") {
		t.Errorf("preview missing run text; html=%s", view.PreviewHTML)
	}
	// Two paragraphs.
	if n := strings.Count(view.PreviewHTML, "<p>"); n != 2 {
		t.Errorf("paragraph count = %d; want 2", n)
	}
	if len(view.Slots) != 0 {
		t.Errorf("fresh doc should have no slots; got %v", view.Slots)
	}
}

func TestImportForAuthoring_DetectsExistingSlots(t *testing.T) {
	body := docBody(`<w:p><w:r><w:t>Az. {{project.case_number}} vor {{project.court}}</w:t></w:r></w:p>`)
	view, err := ImportForAuthoring(minimalMergeDOCX(t, body))
	if err != nil {
		t.Fatalf("ImportForAuthoring: %v", err)
	}
	if len(view.Slots) != 2 {
		t.Fatalf("slots = %d; want 2 (%v)", len(view.Slots), view.Slots)
	}
	if view.Slots[0].Key != "project.case_number" || view.Slots[0].Anchor != "{{project.case_number}}" {
		t.Errorf("slot[0] = %+v; want project.case_number", view.Slots[0])
	}
	if view.Slots[1].Key != "project.court" {
		t.Errorf("slot[1].Key = %q; want project.court", view.Slots[1].Key)
	}
}

func TestInjectSlot_ReplacesSelectionWithPlaceholder(t *testing.T) {
	body := docBody(`<w:p><w:r><w:t>Az. 4c O 12/23 vor dem LG</w:t></w:r></w:p>`)
	out, err := InjectSlot(minimalMergeDOCX(t, body), 0, "4c O 12/23", "project.case_number")
	if err != nil {
		t.Fatalf("InjectSlot: %v", err)
	}
	doc := readMergeDocumentXML(t, out)
	if !strings.Contains(doc, "Az. {{project.case_number}} vor dem LG") {
		t.Errorf("injected doc wrong; got %s", doc)
	}
	// Round-trips: re-importing finds the new slot.
	view, err := ImportForAuthoring(out)
	if err != nil {
		t.Fatalf("re-import: %v", err)
	}
	if len(view.Slots) != 1 || view.Slots[0].Key != "project.case_number" {
		t.Errorf("re-imported slots = %v; want [project.case_number]", view.Slots)
	}
}

func TestInjectSlot_TargetsTheNamedRun(t *testing.T) {
	// "GmbH" appears in run 1 only; "Müller" (with umlaut) in run 0.
	body := docBody(
		`<w:p><w:r><w:t>Müller</w:t></w:r><w:r><w:t xml:space="preserve"> GmbH</w:t></w:r></w:p>`)
	out, err := InjectSlot(minimalMergeDOCX(t, body), 0, "Müller", "parties.claimant.name")
	if err != nil {
		t.Fatalf("InjectSlot: %v", err)
	}
	doc := readMergeDocumentXML(t, out)
	if !strings.Contains(doc, "{{parties.claimant.name}}") {
		t.Errorf("umlaut selection not replaced; got %s", doc)
	}
	if !strings.Contains(doc, " GmbH") {
		t.Errorf("run 1 should be untouched; got %s", doc)
	}
}

func TestInjectSlot_ErrorsWhenSelectionNotInRun(t *testing.T) {
	body := docBody(`<w:p><w:r><w:t>Hello</w:t></w:r></w:p>`)
	if _, err := InjectSlot(minimalMergeDOCX(t, body), 0, "Goodbye", "firm.name"); err == nil {
		t.Error("expected error when selection absent from run; got nil")
	}
	// Out-of-range run index.
	if _, err := InjectSlot(minimalMergeDOCX(t, body), 9, "Hello", "firm.name"); err == nil {
		t.Error("expected error for out-of-range run index; got nil")
	}
}

func TestInjectSlot_RejectsInvalidSlotKey(t *testing.T) {
	body := docBody(`<w:p><w:r><w:t>Hello</w:t></w:r></w:p>`)
	if _, err := InjectSlot(minimalMergeDOCX(t, body), 0, "Hello", "9bad-key!"); err == nil {
		t.Error("expected error for invalid slot key; got nil")
	}
}
