package upc

import (
	"context"
	"testing"
	"time"

	lp "mgit.msbls.de/m/paliad/pkg/litigationplanner"
)

// TestSnapshotMeta loads + parses meta.json and asserts the version
// + non-zero counts. Until the operator regenerates the snapshot the
// placeholder shipped with Slice C must still parse cleanly.
func TestSnapshotMeta(t *testing.T) {
	meta, err := LoadMeta()
	if err != nil {
		t.Fatalf("LoadMeta: %v", err)
	}
	if meta.Version == "" {
		t.Error("meta.Version is empty")
	}
	if meta.ProceedingCount <= 0 {
		t.Errorf("meta.ProceedingCount = %d, want > 0", meta.ProceedingCount)
	}
	if meta.RuleCount <= 0 {
		t.Errorf("meta.RuleCount = %d, want > 0", meta.RuleCount)
	}
}

// TestSnapshotCatalog smoke-tests the embedded catalog's lookups
// against the shipped placeholder. After operator regeneration the
// asserts on per-row content still hold because they pin the wire
// shape (proceedingType.Code, rule resolution by code, lookup-events
// jurisdiction filter).
func TestSnapshotCatalog(t *testing.T) {
	cat, err := NewCatalog()
	if err != nil {
		t.Fatalf("NewCatalog: %v", err)
	}
	ctx := context.Background()

	t.Run("LoadProceeding upc.inf.cfi", func(t *testing.T) {
		pt, rules, err := cat.LoadProceeding(ctx, "upc.inf.cfi", lp.ProjectHint{})
		if err != nil {
			t.Fatalf("LoadProceeding: %v", err)
		}
		if pt.Code != "upc.inf.cfi" {
			t.Errorf("pt.Code = %q, want upc.inf.cfi", pt.Code)
		}
		if pt.Jurisdiction == nil || *pt.Jurisdiction != "UPC" {
			t.Errorf("pt.Jurisdiction = %v, want UPC", pt.Jurisdiction)
		}
		if len(rules) == 0 {
			t.Error("LoadProceeding returned zero rules — snapshot empty?")
		}
	})

	t.Run("LoadProceeding unknown code returns ErrUnknownProceedingType", func(t *testing.T) {
		_, _, err := cat.LoadProceeding(ctx, "no.such.code", lp.ProjectHint{})
		if err != lp.ErrUnknownProceedingType {
			t.Errorf("got %v, want ErrUnknownProceedingType", err)
		}
	})

	t.Run("LookupEvents UPC all-following returns the whole UPC corpus", func(t *testing.T) {
		matches, err := cat.LookupEvents(ctx, lp.EventLookupAxes{
			Jurisdiction: "UPC",
		}, lp.EventLookupDepthAllFollowing)
		if err != nil {
			t.Fatalf("LookupEvents: %v", err)
		}
		if len(matches) == 0 {
			t.Fatal("expected non-empty UPC corpus")
		}
		for _, m := range matches {
			if m.ProceedingType.Jurisdiction == nil || *m.ProceedingType.Jurisdiction != "UPC" {
				t.Errorf("non-UPC row leaked: %v", m.ProceedingType.Code)
			}
			if m.DepthFromAnchor < 1 {
				t.Errorf("depth = %d, want >= 1", m.DepthFromAnchor)
			}
		}
	})

	t.Run("LookupEvents party=defendant scopes anchors", func(t *testing.T) {
		matches, err := cat.LookupEvents(ctx, lp.EventLookupAxes{
			Jurisdiction: "UPC",
			Party:        "defendant",
		}, lp.EventLookupDepthNext)
		if err != nil {
			t.Fatalf("LookupEvents: %v", err)
		}
		// Anchor rows (depth=1) must all be defendant.
		anyDefendant := false
		for _, m := range matches {
			if m.DepthFromAnchor != 1 {
				continue
			}
			if m.Rule.PrimaryParty == nil || *m.Rule.PrimaryParty != "defendant" {
				t.Errorf("anchor row %s is not defendant: %v", m.Rule.Name, m.Rule.PrimaryParty)
			}
			anyDefendant = true
		}
		if !anyDefendant {
			t.Log("no defendant rules in the placeholder corpus — operator should regenerate the snapshot")
		}
	})
}

// TestSnapshotEngineCompute runs the litigationplanner engine against
// the embedded snapshot end-to-end. Ensures the wiring between the
// snapshot Catalog / HolidayCalendar / CourtRegistry + the engine
// produces a non-empty timeline.
func TestSnapshotEngineCompute(t *testing.T) {
	cat, err := NewCatalog()
	if err != nil {
		t.Fatalf("NewCatalog: %v", err)
	}
	hc, err := NewHolidayCalendar()
	if err != nil {
		t.Fatalf("NewHolidayCalendar: %v", err)
	}
	cr, err := NewCourtRegistry()
	if err != nil {
		t.Fatalf("NewCourtRegistry: %v", err)
	}

	ctx := context.Background()
	timeline, err := lp.Calculate(ctx, "upc.inf.cfi", "2026-01-15", lp.CalcOptions{}, cat, hc, cr)
	if err != nil {
		t.Fatalf("Calculate: %v", err)
	}
	if timeline == nil {
		t.Fatal("Calculate returned nil timeline")
	}
	if timeline.ProceedingType != "upc.inf.cfi" {
		t.Errorf("timeline.ProceedingType = %q, want upc.inf.cfi", timeline.ProceedingType)
	}
	if len(timeline.Deadlines) == 0 {
		t.Error("timeline has zero deadlines — snapshot empty?")
	}
}

// TestSnapshotHolidayCalendar smoke-tests the embedded calendar.
// Pins core semantics: weekends are non-working; holidays at
// matching country/regime are non-working; mismatches don't fire.
func TestSnapshotHolidayCalendar(t *testing.T) {
	hc, err := NewHolidayCalendar()
	if err != nil {
		t.Fatalf("NewHolidayCalendar: %v", err)
	}

	// 2026-01-03 is a Saturday — weekend, non-working regardless of
	// country/regime.
	sat := time.Date(2026, 1, 3, 0, 0, 0, 0, time.UTC)
	if !hc.IsNonWorkingDay(sat, "DE", "UPC") {
		t.Error("Saturday should be non-working")
	}

	// 2026-01-01 is Neujahr (DE closure) — non-working when country=DE.
	newYear := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
	if !hc.IsNonWorkingDay(newYear, "DE", "UPC") {
		t.Error("Neujahr should be non-working for DE")
	}

	// 2026-01-05 is a Monday — working (not in holidays, not weekend).
	mon := time.Date(2026, 1, 5, 0, 0, 0, 0, time.UTC)
	if hc.IsNonWorkingDay(mon, "DE", "UPC") {
		t.Error("Monday 2026-01-05 should be working")
	}

	// AdjustForNonWorkingDays from a Saturday should land on Monday.
	adj, _, was := hc.AdjustForNonWorkingDays(sat, "DE", "UPC")
	if !was {
		t.Error("expected adjustment for Saturday")
	}
	if adj.Weekday() != time.Monday {
		t.Errorf("adjusted weekday = %v, want Monday", adj.Weekday())
	}

	// t-paliad-332: UPC vacations are informational only — a deadline
	// landing on a vacation day must NOT be rolled forward. Mirrors
	// the paliad-side policy fixed in t-paliad-121 (the Court keeps
	// running through judicial vacations, so vacation rows live in
	// the snapshot for label payloads but don't extend deadlines).
	//
	// 2026-08-04 is a Tuesday inside UPC Summer Vacation — must stay
	// put on the (DE, UPC) calendar.
	sommerpauseDay := time.Date(2026, 8, 4, 0, 0, 0, 0, time.UTC)
	if sommerpauseDay.Weekday() == time.Saturday || sommerpauseDay.Weekday() == time.Sunday {
		t.Fatalf("test premise broken: 2026-08-04 should not be a weekend (got %v)",
			sommerpauseDay.Weekday())
	}
	if hc.IsNonWorkingDay(sommerpauseDay, "DE", "UPC") {
		t.Error("UPC Summer Vacation weekday must not be non-working (t-paliad-332)")
	}
	adjV, _, wasV := hc.AdjustForNonWorkingDays(sommerpauseDay, "DE", "UPC")
	if wasV {
		t.Error("expected NO adjustment for vacation-only day (t-paliad-332)")
	}
	if !adjV.Equal(sommerpauseDay) {
		t.Errorf("adjusted = %v, want %v (vacation must not roll, t-paliad-332)",
			adjV.Format("2006-01-02"), sommerpauseDay.Format("2006-01-02"))
	}

	// Sanity-pin: a UPC Winter Vacation date that is ALSO adjacent
	// to weekend + Neujahr (the scenario m flagged on youpc.org —
	// "rolled from 2027-01-02 (UPC Winter Vacation)"). 2027-01-02 is
	// a Saturday; the roll must cross Sat/Sun → Mon 2027-01-04, which
	// is in UPC Winter Vacation but no longer blocks → stops there.
	// Pre-fix this rolled all the way to Thu 2027-01-07.
	jan2 := time.Date(2027, 1, 2, 0, 0, 0, 0, time.UTC)
	adjW, _, wasW := hc.AdjustForNonWorkingDays(jan2, "DE", "UPC")
	if !wasW {
		t.Error("Sat 2027-01-02 must roll forward (weekend)")
	}
	want := time.Date(2027, 1, 4, 0, 0, 0, 0, time.UTC)
	if !adjW.Equal(want) {
		t.Errorf("Sat 2027-01-02 adjusted to %v, want %v (vacation no longer rolls, t-paliad-332)",
			adjW.Format("2006-01-02"), want.Format("2006-01-02"))
	}
}

// TestSnapshotCourtRegistry pins (country, regime) resolution.
func TestSnapshotCourtRegistry(t *testing.T) {
	cr, err := NewCourtRegistry()
	if err != nil {
		t.Fatalf("NewCourtRegistry: %v", err)
	}

	t.Run("empty courtID falls back to defaults", func(t *testing.T) {
		c, r, err := cr.CountryRegime("", "DE", "UPC")
		if err != nil {
			t.Fatalf("CountryRegime: %v", err)
		}
		if c != "DE" || r != "UPC" {
			t.Errorf("got (%q, %q), want (DE, UPC)", c, r)
		}
	})

	t.Run("known UPC court resolves", func(t *testing.T) {
		c, r, err := cr.CountryRegime("upc-ld-munich", "DE", "")
		if err != nil {
			t.Fatalf("CountryRegime: %v", err)
		}
		if c != "DE" || r != "UPC" {
			t.Errorf("got (%q, %q), want (DE, UPC)", c, r)
		}
	})

	t.Run("unknown court returns error", func(t *testing.T) {
		_, _, err := cr.CountryRegime("not-a-court", "DE", "UPC")
		if err == nil {
			t.Error("expected error for unknown court")
		}
	})
}
