package services

import (
	"context"
	"os"
	"testing"

	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq"

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

// TestLookupEvents covers the multi-axis catalog query API from Slice
// B2 (m/paliad#124 §18.2). Skipped when TEST_DATABASE_URL is unset,
// mirroring TestCalculateRule.
//
// Cases:
//   - jurisdiction=UPC, depth=all-following → every active+published
//     UPC rule, anchor depth=1 for all (no parent_id outside the
//     filtered set lights up depth>1 because the entire UPC subset is
//     a single anchor cohort).
//   - proceeding_type_id (upc.inf.cfi) + party=defendant + depth=next
//     → defendant rules in upc.inf.cfi at depth=1 + direct children
//     of those at depth=2.
//   - unknown jurisdiction value → silently ignored, no filter applied.
//   - empty axes → all rules (no filter on any axis).
func TestLookupEvents(t *testing.T) {
	url := os.Getenv("TEST_DATABASE_URL")
	if url == "" {
		t.Skip("TEST_DATABASE_URL not set — skipping live DB test")
	}
	if err := db.ApplyMigrations(url); err != nil {
		t.Fatalf("apply migrations: %v", err)
	}
	pool, err := sqlx.Connect("postgres", url)
	if err != nil {
		t.Fatalf("connect: %v", err)
	}
	defer pool.Close()

	ctx := context.Background()
	rules := NewDeadlineRuleService(pool)
	catalog := &paliadCatalog{rules: rules}

	t.Run("jurisdiction=UPC, all-following returns the UPC corpus", func(t *testing.T) {
		matches, err := catalog.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")
		}
		// Every match must be a UPC rule.
		for _, m := range matches {
			if m.ProceedingType.Jurisdiction == nil || *m.ProceedingType.Jurisdiction != "UPC" {
				t.Errorf("non-UPC row leaked into UPC-axis query: code=%s jurisdiction=%v",
					m.ProceedingType.Code, m.ProceedingType.Jurisdiction)
			}
			if m.DepthFromAnchor < 1 {
				t.Errorf("depth=%d for rule %s, want >= 1", m.DepthFromAnchor, m.Rule.ID)
			}
		}
	})

	t.Run("party=defendant scopes to defendant rules", func(t *testing.T) {
		matches, err := catalog.LookupEvents(ctx, lp.EventLookupAxes{
			Jurisdiction: "UPC",
			Party:        "defendant",
		}, lp.EventLookupDepthNext)
		if err != nil {
			t.Fatalf("LookupEvents: %v", err)
		}
		if len(matches) == 0 {
			t.Fatal("expected at least one defendant rule across the UPC corpus")
		}
		// Anchor matches (depth=1) must be primary_party=defendant.
		// Depth=2 children appear under EventLookupDepthNext only as
		// expansion from anchors — they may carry any party.
		for _, m := range matches {
			if m.DepthFromAnchor != 1 {
				continue
			}
			if m.Rule.PrimaryParty == nil || *m.Rule.PrimaryParty != "defendant" {
				t.Errorf("anchor row %s (depth=1) is not defendant: %v",
					m.Rule.Name, m.Rule.PrimaryParty)
			}
		}
	})

	t.Run("unknown jurisdiction value silently falls through", func(t *testing.T) {
		matchesAll, err := catalog.LookupEvents(ctx, lp.EventLookupAxes{},
			lp.EventLookupDepthAllFollowing)
		if err != nil {
			t.Fatalf("LookupEvents (all): %v", err)
		}
		matchesUnknown, err := catalog.LookupEvents(ctx, lp.EventLookupAxes{
			Jurisdiction: "XX-not-a-real-jurisdiction",
		}, lp.EventLookupDepthAllFollowing)
		if err != nil {
			t.Fatalf("LookupEvents (unknown): %v", err)
		}
		if len(matchesAll) != len(matchesUnknown) {
			t.Errorf("unknown jurisdiction should fall through to no-filter; got %d vs all-axes %d",
				len(matchesUnknown), len(matchesAll))
		}
	})

	t.Run("appeal_target=endentscheidung returns upc.apl merits rules", func(t *testing.T) {
		matches, err := catalog.LookupEvents(ctx, lp.EventLookupAxes{
			Jurisdiction: "UPC",
			AppealTarget: lp.AppealTargetEndentscheidung,
		}, lp.EventLookupDepthAllFollowing)
		if err != nil {
			t.Fatalf("LookupEvents: %v", err)
		}
		// Should hit the 7 merits-track rules that carry
		// applies_to_target={endentscheidung} (Slice B1 mig 134).
		// Post-mig 155 (m/paliad#149 P1): the unified upc.apl was split
		// back into merits/cost/order — the endentscheidung anchors live
		// under upc.apl.merits (id=11).
		if len(matches) == 0 {
			t.Fatal("expected upc.apl.merits endentscheidung rules after B1 mig")
		}
		for _, m := range matches {
			if m.DepthFromAnchor != 1 {
				continue // children of anchors may be from other targets
			}
			found := false
			for _, t := range m.Rule.AppliesToTarget {
				if t == lp.AppealTargetEndentscheidung {
					found = true
					break
				}
			}
			if !found {
				t.Errorf("anchor row %s missing endentscheidung target: %v",
					m.Rule.Name, m.Rule.AppliesToTarget)
			}
			if m.ProceedingType.Code != "upc.apl.merits" {
				t.Errorf("anchor row %s came from %s, want upc.apl.merits",
					m.Rule.Name, m.ProceedingType.Code)
			}
		}
	})

	t.Run("appeal_target=schadensbemessung returns upc.apl merits rules (mig 138 backfill)", func(t *testing.T) {
		matches, err := catalog.LookupEvents(ctx, lp.EventLookupAxes{
			Jurisdiction: "UPC",
			AppealTarget: lp.AppealTargetSchadensbemessung,
		}, lp.EventLookupDepthAllFollowing)
		if err != nil {
			t.Fatalf("LookupEvents: %v", err)
		}
		// mig 138 (t-paliad-303, m/paliad#134) extends the 7 merits-track
		// rules with applies_to_target ⊇ {schadensbemessung} because
		// R.224 is uniform across substantive R.118 decisions. Post-mig
		// 155 the merits track lives at upc.apl.merits (id=11).
		if len(matches) == 0 {
			t.Fatal("expected upc.apl.merits schadensbemessung rules after mig 138 backfill")
		}
		for _, m := range matches {
			if m.DepthFromAnchor != 1 {
				continue
			}
			found := false
			for _, t := range m.Rule.AppliesToTarget {
				if t == lp.AppealTargetSchadensbemessung {
					found = true
					break
				}
			}
			if !found {
				t.Errorf("anchor row %s missing schadensbemessung target: %v",
					m.Rule.Name, m.Rule.AppliesToTarget)
			}
			if m.ProceedingType.Code != "upc.apl.merits" {
				t.Errorf("anchor row %s came from %s, want upc.apl.merits",
					m.Rule.Name, m.ProceedingType.Code)
			}
		}
	})

	t.Run("appeal_target=bucheinsicht returns upc.apl order rules (mig 138 backfill)", func(t *testing.T) {
		matches, err := catalog.LookupEvents(ctx, lp.EventLookupAxes{
			Jurisdiction: "UPC",
			AppealTarget: lp.AppealTargetBucheinsicht,
		}, lp.EventLookupDepthAllFollowing)
		if err != nil {
			t.Fatalf("LookupEvents: %v", err)
		}
		// mig 138 (t-paliad-303, m/paliad#134) extends the 7 order-track
		// rules with applies_to_target ⊇ {bucheinsicht} because R.220.2 /
		// R.224.2.b / R.235.2 / R.237 / R.238.2 are uniform across the
		// orders they appeal. Post-mig 155 the order track lives at
		// upc.apl.order (id=20).
		if len(matches) == 0 {
			t.Fatal("expected upc.apl.order bucheinsicht rules after mig 138 backfill")
		}
		for _, m := range matches {
			if m.DepthFromAnchor != 1 {
				continue
			}
			found := false
			for _, t := range m.Rule.AppliesToTarget {
				if t == lp.AppealTargetBucheinsicht {
					found = true
					break
				}
			}
			if !found {
				t.Errorf("anchor row %s missing bucheinsicht target: %v",
					m.Rule.Name, m.Rule.AppliesToTarget)
			}
			if m.ProceedingType.Code != "upc.apl.order" {
				t.Errorf("anchor row %s came from %s, want upc.apl.order",
					m.Rule.Name, m.ProceedingType.Code)
			}
		}
	})
}
