package services

import (
	"reflect"
	"testing"
)

func TestMapLitigationToFristenrechner(t *testing.T) {
	type tc struct {
		litigation, jurisdiction string
		wantCode                 string
		wantFlags                []string
		wantOK                   bool
	}
	cases := []tc{
		// Unambiguous UPC fold-ins.
		{"INF", "UPC", CodeUPCInfringement, nil, true},
		{"REV", "UPC", CodeUPCRevocation, nil, true},
		{"APP", "UPC", CodeUPCAppealMerits, nil, true},
		{"APM", "UPC", CodeUPCPreliminary, nil, true},
		// CCR + UPC = upc.inf.cfi with the with_ccr flag.
		{"CCR", "UPC", CodeUPCInfringement, []string{"with_ccr"}, true},
		// AMD + UPC = upc.inf.cfi with the with_amend flag.
		{"AMD", "UPC", CodeUPCInfringement, []string{"with_amend"}, true},
		// DE first-instance / Nichtigkeit mappings.
		{"INF", "DE", CodeDEInfringementLG, nil, true},
		{"REV", "DE", CodeDENullityBPatG, nil, true},
		{"CCR", "DE", CodeDENullityBPatG, nil, true},
		// EPA opposition.
		{"OPP", "EPA", CodeEPAOpposition, nil, true},
		// Ambiguous: APP+DE has both OLG and BGH analogues; project
		// model can't disambiguate, so degrade.
		{"APP", "DE", "", nil, false},
		// No analogue: ZPO_CIVIL → nothing in fristenrechner.
		{"ZPO_CIVIL", "DE", "", nil, false},
		// AMD only fires on UPC; DE has no analogue.
		{"AMD", "DE", "", nil, false},
		// APM only fires on UPC.
		{"APM", "EPA", "", nil, false},
		// Unknown codes / jurisdictions → ok=false.
		{"XXX", "UPC", "", nil, false},
		{"INF", "ZZZ", "", nil, false},
		{"", "", "", nil, false},
	}
	for _, c := range cases {
		gotCode, gotFlags, gotOK := MapLitigationToFristenrechner(c.litigation, c.jurisdiction)
		if gotCode != c.wantCode || gotOK != c.wantOK || !reflect.DeepEqual(gotFlags, c.wantFlags) {
			t.Errorf("MapLitigationToFristenrechner(%q, %q) = (%q, %v, %v); want (%q, %v, %v)",
				c.litigation, c.jurisdiction,
				gotCode, gotFlags, gotOK,
				c.wantCode, c.wantFlags, c.wantOK)
		}
	}
}

func TestResolveCounterclaimRouting(t *testing.T) {
	t.Run("upc.ccr.cfi routes to upc.inf.cfi with with_ccr", func(t *testing.T) {
		gotCode, gotFlags, routed := ResolveCounterclaimRouting(CodeUPCCounterclaim)
		if gotCode != CodeUPCInfringement {
			t.Errorf("effective code = %q, want %q", gotCode, CodeUPCInfringement)
		}
		if !reflect.DeepEqual(gotFlags, []string{"with_ccr"}) {
			t.Errorf("default flags = %v, want [with_ccr]", gotFlags)
		}
		if !routed {
			t.Errorf("routed = false, want true")
		}
	})
	t.Run("non-ccr code passes through unchanged", func(t *testing.T) {
		for _, code := range []string{CodeUPCInfringement, CodeUPCRevocation, CodeDEInfringementLG, "anything-else"} {
			gotCode, gotFlags, routed := ResolveCounterclaimRouting(code)
			if gotCode != code {
				t.Errorf("ResolveCounterclaimRouting(%q) returned %q, want pass-through", code, gotCode)
			}
			if gotFlags != nil {
				t.Errorf("ResolveCounterclaimRouting(%q) flags = %v, want nil", code, gotFlags)
			}
			if routed {
				t.Errorf("ResolveCounterclaimRouting(%q) routed = true, want false", code)
			}
		}
	})
}

// TestSubTrackRoutings asserts the registry shape m/paliad#58 depends
// on: every entry's Code matches its map key, has a non-empty
// ParentCode + DefaultFlags + bilingual notes. Drift here silently
// breaks the spawn-as-standalone renderer (a CCR pick would 404 or
// render an empty timeline), so we pin the contract.
func TestSubTrackRoutings(t *testing.T) {
	if len(SubTrackRoutings) == 0 {
		t.Fatal("SubTrackRoutings is empty — at minimum upc.ccr.cfi must be registered")
	}
	for key, route := range SubTrackRoutings {
		if route.Code != key {
			t.Errorf("SubTrackRoutings[%q].Code = %q, want %q (key/value mismatch)", key, route.Code, key)
		}
		if route.ParentCode == "" {
			t.Errorf("SubTrackRoutings[%q] has empty ParentCode", key)
		}
		if len(route.DefaultFlags) == 0 {
			t.Errorf("SubTrackRoutings[%q] has no DefaultFlags — sub-track routing without flags is a no-op", key)
		}
		if route.NoteDE == "" || route.NoteEN == "" {
			t.Errorf("SubTrackRoutings[%q] missing bilingual note: DE=%q EN=%q", key, route.NoteDE, route.NoteEN)
		}
	}
	// CCR is the canonical entry — assert its exact shape so a future
	// rename doesn't silently change semantics.
	ccr, ok := LookupSubTrackRouting(CodeUPCCounterclaim)
	if !ok {
		t.Fatal("LookupSubTrackRouting(upc.ccr.cfi) returned ok=false; entry must be registered")
	}
	if ccr.ParentCode != CodeUPCInfringement {
		t.Errorf("CCR.ParentCode = %q, want %q", ccr.ParentCode, CodeUPCInfringement)
	}
	if !reflect.DeepEqual(ccr.DefaultFlags, []string{"with_ccr"}) {
		t.Errorf("CCR.DefaultFlags = %v, want [with_ccr]", ccr.DefaultFlags)
	}
	if _, miss := LookupSubTrackRouting(CodeUPCInfringement); miss {
		t.Error("LookupSubTrackRouting(upc.inf.cfi) returned ok=true; non-sub-track codes must miss")
	}
}
