package services

import (
	"testing"

	"github.com/google/uuid"
)

// TestProjectCodeSegment pins the per-type segment derivation rules
// from t-paliad-222 design §3.2:
//
//   client     → reference if set, else sanitized title (cap 8 chars)
//   litigation → opponent_code verbatim (empty → skipped)
//   patent     → last 3 digits of patent_number
//   case       → uppercase tail of proceeding_types.code
//   project    → ""
func TestProjectCodeSegment(t *testing.T) {
	str := func(s string) *string { return &s }
	intp := func(i int) *int { return &i }

	cases := []struct {
		name string
		row  projectChainRow
		want string
	}{
		// Client rows.
		{
			"client with reference",
			projectChainRow{Type: "client", Title: "Example Co.", Reference: str("EXMPL")},
			"EXMPL",
		},
		{
			"client without reference falls back to slug(title)",
			projectChainRow{Type: "client", Title: "Example Co.", Reference: nil},
			"EXAMPLEC",
		},
		{
			"client without reference, diacritics stripped",
			projectChainRow{Type: "client", Title: "Müller GmbH"},
			"MULLERGM",
		},
		{
			"client with empty reference falls back to title",
			projectChainRow{Type: "client", Title: "ACME", Reference: str("  ")},
			"ACME",
		},
		{
			"client with empty title and no reference → empty",
			projectChainRow{Type: "client", Title: ""},
			"",
		},

		// Litigation rows.
		{
			"litigation with opponent_code",
			projectChainRow{Type: "litigation", Title: "X v Y", OpponentCode: str("OPNT")},
			"OPNT",
		},
		{
			"litigation without opponent_code → empty",
			projectChainRow{Type: "litigation", Title: "X v Y", OpponentCode: nil},
			"",
		},

		// Patent rows.
		{
			"patent EP1234567 → 567",
			projectChainRow{Type: "patent", PatentNumber: str("EP1234567")},
			"567",
		},
		{
			"patent with spaces EP 3 456 789 → 789",
			projectChainRow{Type: "patent", PatentNumber: str("EP 3 456 789")},
			"789",
		},
		{
			"patent with kind code EP3456789A1 → 789",
			projectChainRow{Type: "patent", PatentNumber: str("EP3456789A1")},
			"789",
		},
		{
			"patent WO2020/123456 → 456",
			projectChainRow{Type: "patent", PatentNumber: str("WO2020/123456")},
			"456",
		},
		{
			"patent shorter than 3 digits → full",
			projectChainRow{Type: "patent", PatentNumber: str("DE12")},
			"12",
		},
		{
			"patent nil → empty",
			projectChainRow{Type: "patent", PatentNumber: nil},
			"",
		},
		{
			"patent empty digit-stream → empty",
			projectChainRow{Type: "patent", PatentNumber: str("EP")},
			"",
		},

		// Case rows.
		{
			"case upc.inf.cfi → INF.CFI",
			projectChainRow{Type: "case", ProceedingTypeID: intp(8), ProceedingCode: str("upc.inf.cfi")},
			"INF.CFI",
		},
		{
			"case upc.apl.merits → APL.MERITS",
			projectChainRow{Type: "case", ProceedingTypeID: intp(11), ProceedingCode: str("upc.apl.merits")},
			"APL.MERITS",
		},
		{
			"case de.inf.lg → INF.LG",
			projectChainRow{Type: "case", ProceedingTypeID: intp(12), ProceedingCode: str("de.inf.lg")},
			"INF.LG",
		},
		{
			"case without proceeding_code → empty",
			projectChainRow{Type: "case", ProceedingTypeID: nil, ProceedingCode: nil},
			"",
		},
		{
			"case with single-segment code → empty (no tail)",
			projectChainRow{Type: "case", ProceedingCode: str("single")},
			"",
		},

		// Generic project rows contribute nothing.
		{
			"generic project → empty",
			projectChainRow{Type: "project", Title: "Whatever"},
			"",
		},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			got := projectCodeSegment(c.row)
			if got != c.want {
				t.Errorf("projectCodeSegment() = %q, want %q", got, c.want)
			}
		})
	}
}

// TestAssembleProjectCode covers the chain assembler, including the
// custom-override fast-path on the target row's `reference`.
func TestAssembleProjectCode(t *testing.T) {
	str := func(s string) *string { return &s }
	intp := func(i int) *int { return &i }

	// The reference tree from the issue body: EXMPL → OPNT → EP3456789 → upc.inf.cfi.
	fullChain := []projectChainRow{
		{ID: uuid.New(), Type: "client", Title: "Example Co.", Reference: str("EXMPL")},
		{ID: uuid.New(), Type: "litigation", Title: "Ex v Op", OpponentCode: str("OPNT")},
		{ID: uuid.New(), Type: "patent", PatentNumber: str("EP3456789")},
		{ID: uuid.New(), Type: "case", ProceedingTypeID: intp(8), ProceedingCode: str("upc.inf.cfi")},
	}

	cases := []struct {
		name  string
		chain []projectChainRow
		want  string
	}{
		{
			"reference tree → EXMPL.OPNT.789.INF.CFI",
			fullChain,
			"EXMPL.OPNT.789.INF.CFI",
		},
		{
			"empty chain → empty",
			nil,
			"",
		},
		{
			"override on target wins outright",
			append(append([]projectChainRow{}, fullChain[:3]...), projectChainRow{
				ID: uuid.New(), Type: "case", Reference: str("CUSTOM-CODE"),
				ProceedingTypeID: intp(8), ProceedingCode: str("upc.inf.cfi"),
			}),
			"CUSTOM-CODE",
		},
		{
			"override with surrounding whitespace is trimmed",
			append(append([]projectChainRow{}, fullChain[:3]...), projectChainRow{
				ID: uuid.New(), Type: "case", Reference: str("  TRIMMED  "),
			}),
			"TRIMMED",
		},
		{
			"override empty string falls through to derivation",
			append(append([]projectChainRow{}, fullChain[:3]...), projectChainRow{
				ID: uuid.New(), Type: "case", Reference: str(""),
				ProceedingTypeID: intp(8), ProceedingCode: str("upc.inf.cfi"),
			}),
			"EXMPL.OPNT.789.INF.CFI",
		},
		{
			"missing ancestors are skipped silently — case directly under client",
			[]projectChainRow{
				{Type: "client", Reference: str("EXMPL")},
				{Type: "case", ProceedingCode: str("upc.inf.cfi")},
			},
			"EXMPL.INF.CFI",
		},
		{
			"missing patent contributes nothing; client+litigation+case",
			[]projectChainRow{
				{Type: "client", Reference: str("EXMPL")},
				{Type: "litigation", OpponentCode: str("OPNT")},
				{Type: "case", ProceedingCode: str("upc.inf.cfi")},
			},
			"EXMPL.OPNT.INF.CFI",
		},
		{
			"target itself is a litigation row (no case below) → up to opponent code",
			[]projectChainRow{
				{Type: "client", Reference: str("EXMPL")},
				{Type: "litigation", OpponentCode: str("OPNT")},
			},
			"EXMPL.OPNT",
		},
		{
			"litigation without opponent_code is skipped silently",
			[]projectChainRow{
				{Type: "client", Reference: str("EXMPL")},
				{Type: "litigation", OpponentCode: nil},
				{Type: "patent", PatentNumber: str("EP3456789")},
				{Type: "case", ProceedingCode: str("upc.inf.cfi")},
			},
			"EXMPL.789.INF.CFI",
		},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			got := assembleProjectCode(c.chain)
			if got != c.want {
				t.Errorf("assembleProjectCode() = %q, want %q", got, c.want)
			}
		})
	}
}

// TestPatentLast3 pins the digit-extraction rule across the common
// patent-number formats users type.
func TestPatentLast3(t *testing.T) {
	cases := []struct {
		in, want string
	}{
		{"EP1234567", "567"},
		{"EP 1 234 567", "567"},
		{"EP3456789A1", "789"},
		{"WO2020/123456A1", "456"},
		{"DE12", "12"},
		{"EP", ""},
		{"", ""},
		{"NoDigitsAtAll", ""},
		{"1", "1"},
		{"12", "12"},
		{"123", "123"},
		{"1234", "234"},
	}
	for _, c := range cases {
		t.Run(c.in, func(t *testing.T) {
			if got := patentLast3(c.in); got != c.want {
				t.Errorf("patentLast3(%q) = %q, want %q", c.in, got, c.want)
			}
		})
	}
}

// TestSanitizeClientShort pins the client-short slug rule (uppercase,
// strip diacritics, drop non-alnum, cap 8).
func TestSanitizeClientShort(t *testing.T) {
	cases := []struct {
		in, want string
	}{
		{"EXMPL", "EXMPL"},
		{"Example Co.", "EXAMPLEC"},
		{"Müller GmbH", "MULLERGM"},
		{"  ACME  ", "ACME"},
		{"", ""},
		{"   ", ""},
		{"Hogan Lovells International LLP", "HOGANLOV"},
		{"A&B (Patents) Ltd.", "ABPATENT"},
		{"Société Générale", "SOCIETEG"},
	}
	for _, c := range cases {
		t.Run(c.in, func(t *testing.T) {
			if got := sanitizeClientShort(c.in); got != c.want {
				t.Errorf("sanitizeClientShort(%q) = %q, want %q", c.in, got, c.want)
			}
		})
	}
}

// TestProceedingTail pins the jurisdiction-strip rule.
func TestProceedingTail(t *testing.T) {
	cases := []struct {
		in, want string
	}{
		{"upc.inf.cfi", "INF.CFI"},
		{"upc.rev.cfi", "REV.CFI"},
		{"upc.pi.cfi", "PI.CFI"},
		{"upc.apl.merits", "APL.MERITS"},
		{"de.inf.lg", "INF.LG"},
		{"de.inf.olg", "INF.OLG"},
		{"single", ""},
		{"", ""},
		{"a.b", "B"},
		{"  upc.inf.cfi  ", "INF.CFI"},
	}
	for _, c := range cases {
		t.Run(c.in, func(t *testing.T) {
			if got := proceedingTail(c.in); got != c.want {
				t.Errorf("proceedingTail(%q) = %q, want %q", c.in, got, c.want)
			}
		})
	}
}

// TestValidateOpponentCode pins the slug-validation rule + the
// type='litigation' pairing. Empty string is the explicit clear
// sentinel and always passes.
func TestValidateOpponentCode(t *testing.T) {
	cases := []struct {
		name  string
		code  string
		ptype string
		wantE bool
	}{
		{"empty clears, any type", "", "case", false},
		{"empty clears, litigation", "", "litigation", false},
		{"valid slug on litigation", "OPNT", "litigation", false},
		{"valid slug with digits on litigation", "OPNT-2026", "litigation", false},
		{"valid slug projectType empty (Update path)", "OPNT", "", false},
		{"lowercase rejected", "opnt", "litigation", true},
		{"underscore rejected", "OPNT_1", "litigation", true},
		{"too long rejected", "OPNT-AND-A-VERY-LONG-NAME", "litigation", true},
		{"non-litigation type rejected", "OPNT", "case", true},
		{"non-litigation type rejected (patent)", "OPNT", "patent", true},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			err := validateOpponentCode(c.code, c.ptype)
			if (err != nil) != c.wantE {
				t.Errorf("validateOpponentCode(%q, %q) error = %v, wantErr=%v",
					c.code, c.ptype, err, c.wantE)
			}
		})
	}
}

// TestValidateOurSideSubRoles pins the widened allowlist (mig 112).
func TestValidateOurSideSubRoles(t *testing.T) {
	valid := []string{
		"", "claimant", "defendant", "applicant", "appellant",
		"respondent", "third_party", "other",
	}
	invalid := []string{"court", "both", "unknown", "CLAIMANT", "Defendant"}

	for _, v := range valid {
		t.Run("valid_"+v, func(t *testing.T) {
			if err := validateOurSide(v); err != nil {
				t.Errorf("validateOurSide(%q) unexpected error: %v", v, err)
			}
		})
	}
	for _, v := range invalid {
		t.Run("invalid_"+v, func(t *testing.T) {
			if err := validateOurSide(v); err == nil {
				t.Errorf("validateOurSide(%q) expected error, got nil", v)
			}
		})
	}
}
