package services

import (
	"encoding/json"
	"errors"
	"strings"
	"testing"
)

func TestSlugifyTitle(t *testing.T) {
	cases := []struct{ in, want string }{
		{"UPC Klageschrift Strategie", "upc-klageschrift-strategie"},
		{"Hülle für Münch (München!)", "huelle-fuer-muench-muenchen"},
		{"   ", ""},
		{"&&&", ""},
		{"A really really really really long title that ought to be clamped to forty chars max", "a-really-really-really-really-long-title"},
		{"Straße ABC", "strasse-abc"},
		{"---leading-and-trailing---", "leading-and-trailing"},
	}
	for _, c := range cases {
		got := slugifyTitle(c.in)
		if got != c.want {
			t.Errorf("slugifyTitle(%q) = %q; want %q", c.in, got, c.want)
		}
	}
}

func TestNormaliseRegime(t *testing.T) {
	for _, valid := range []string{"upc", "DE", "  epa  ", "Other", ""} {
		if _, err := normaliseRegime(valid); err != nil {
			t.Errorf("normaliseRegime(%q) errored unexpectedly: %v", valid, err)
		}
	}
	if _, err := normaliseRegime("bogus"); !errors.Is(err, ErrInvalidInput) {
		t.Errorf("normaliseRegime(bogus) expected ErrInvalidInput, got %v", err)
	}
}

func TestNormaliseLang(t *testing.T) {
	for _, valid := range []string{"de", "EN", "  ", ""} {
		if _, err := normaliseLang(valid); err != nil {
			t.Errorf("normaliseLang(%q) errored: %v", valid, err)
		}
	}
	if _, err := normaliseLang("fr"); !errors.Is(err, ErrInvalidInput) {
		t.Errorf("normaliseLang(fr) expected ErrInvalidInput, got %v", err)
	}
}

func TestNormaliseSliceAVisibility(t *testing.T) {
	// Slice B opened up 'shared' as a valid author-set visibility
	// (alongside 'private' and 'firm'). 'global' stays admin-only via
	// ChecklistPromotionService.
	for _, valid := range []string{"private", "firm", "shared", "  ", ""} {
		if _, err := normaliseSliceAVisibility(valid); err != nil {
			t.Errorf("visibility(%q) errored: %v", valid, err)
		}
	}
	for _, bad := range []string{"global", "public"} {
		if _, err := normaliseSliceAVisibility(bad); !errors.Is(err, ErrInvalidInput) {
			t.Errorf("visibility(%q) expected ErrInvalidInput, got %v", bad, err)
		}
	}
}

func TestRequireNonEmptyTrimmed(t *testing.T) {
	if _, err := requireNonEmptyTrimmed("  ", "title", 200); !errors.Is(err, ErrInvalidInput) {
		t.Errorf("empty title should be rejected, got %v", err)
	}
	if got, err := requireNonEmptyTrimmed("  hello  ", "title", 200); err != nil || got != "hello" {
		t.Errorf("expected 'hello', got %q (err=%v)", got, err)
	}
	if _, err := requireNonEmptyTrimmed(strings.Repeat("x", 201), "title", 200); !errors.Is(err, ErrInvalidInput) {
		t.Errorf("over-length title should be rejected, got %v", err)
	}
}

func TestValidateBodyShape(t *testing.T) {
	// Happy path: at least one group, at least one item.
	ok := json.RawMessage(`{"groups":[{"titleDE":"G1","titleEN":"G1","items":[{"labelDE":"X","labelEN":"X"}]}]}`)
	if err := validateBodyShape(ok); err != nil {
		t.Errorf("valid body rejected: %v", err)
	}
	// Empty groups.
	if err := validateBodyShape(json.RawMessage(`{"groups":[]}`)); !errors.Is(err, ErrInvalidInput) {
		t.Errorf("empty groups expected ErrInvalidInput, got %v", err)
	}
	// Group with no items.
	if err := validateBodyShape(json.RawMessage(`{"groups":[{"titleDE":"G","titleEN":"G","items":[]}]}`)); !errors.Is(err, ErrInvalidInput) {
		t.Errorf("empty items expected ErrInvalidInput, got %v", err)
	}
	// Missing field.
	if err := validateBodyShape(json.RawMessage(nil)); !errors.Is(err, ErrInvalidInput) {
		t.Errorf("nil body expected ErrInvalidInput, got %v", err)
	}
	// Malformed JSON.
	if err := validateBodyShape(json.RawMessage(`{not json`)); !errors.Is(err, ErrInvalidInput) {
		t.Errorf("malformed body expected ErrInvalidInput, got %v", err)
	}
}

func TestChecklistCatalogIsStaticSlug(t *testing.T) {
	// nil DB is fine — we never touch it in this test.
	cat := NewChecklistCatalogService(nil)
	if !cat.IsStaticSlug("upc-statement-of-claim") {
		t.Error("expected static slug to be detected")
	}
	if cat.IsStaticSlug("u-some-authored-slug") {
		t.Error("unexpected static-slug match for authored slug")
	}
}

func TestChecklistVisibilityPredicate(t *testing.T) {
	got := checklistVisibilityPredicate("c", 1)
	for _, want := range []string{"c.owner_id = $1", "c.visibility IN ('firm', 'global')", "u.global_role = 'global_admin'"} {
		if !strings.Contains(got, want) {
			t.Errorf("predicate missing %q in: %s", want, got)
		}
	}
}

func TestClampFreeText(t *testing.T) {
	if got := clampFreeText("  hello  ", 200); got != "hello" {
		t.Errorf("expected trimmed 'hello', got %q", got)
	}
	if got := clampFreeText(strings.Repeat("x", 250), 200); len(got) != 200 {
		t.Errorf("expected clamp to 200, got len=%d", len(got))
	}
}
