package services

import (
	"context"
	"os"
	"testing"

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

	"mgit.msbls.de/m/paliad/internal/db"
)

// TestSearchEvents covers the ?kind=events response shape for the
// Fristenrechner overhaul S1 (design §6.1). Verified against live data:
// "Klageerhebung" must return upc.inf.cfi.soc (the canonical SoC
// procedural event) as the top hit, with the proceeding metadata
// populated and a non-zero follow_up_count.
//
// Skipped when TEST_DATABASE_URL is unset, mirroring the other live-DB
// tests in this package.
func TestSearchEvents(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()

	svc := NewDeadlineSearchService(pool)

	t.Run("Klageerhebung returns upc.inf.cfi.soc with follow-ups", func(t *testing.T) {
		resp, err := svc.SearchEvents(ctx, "Klageerhebung", EventSearchOptions{Limit: 30})
		if err != nil {
			t.Fatalf("search events: %v", err)
		}
		if len(resp.Events) == 0 {
			t.Fatalf("no events returned for Klageerhebung")
		}
		var soc *EventSearchHit
		for i := range resp.Events {
			if resp.Events[i].Code == "upc.inf.cfi.soc" {
				soc = &resp.Events[i]
				break
			}
		}
		if soc == nil {
			t.Fatalf("upc.inf.cfi.soc not in event hits (got %d hits)", len(resp.Events))
		}
		if soc.NameDE == "" {
			t.Errorf("expected name_de populated, got empty")
		}
		if soc.ProceedingType.Code != "upc.inf.cfi" {
			t.Errorf("expected proceeding upc.inf.cfi, got %q", soc.ProceedingType.Code)
		}
		if soc.FollowUpCount <= 0 {
			t.Errorf("expected follow_up_count > 0 for SoC, got %d", soc.FollowUpCount)
		}
		if soc.EventKind == nil || *soc.EventKind != "filing" {
			gotKind := "<nil>"
			if soc.EventKind != nil {
				gotKind = *soc.EventKind
			}
			t.Errorf("expected event_kind=filing, got %q", gotKind)
		}
	})

	t.Run("jurisdiction filter narrows to UPC", func(t *testing.T) {
		resp, err := svc.SearchEvents(ctx, "", EventSearchOptions{
			Jurisdiction: "UPC",
			Limit:        200,
		})
		if err != nil {
			t.Fatalf("search events UPC: %v", err)
		}
		if len(resp.Events) == 0 {
			t.Fatalf("expected UPC events, got 0")
		}
		for _, e := range resp.Events {
			if e.ProceedingType.Jurisdiction == nil || *e.ProceedingType.Jurisdiction != "UPC" {
				gotJ := "<nil>"
				if e.ProceedingType.Jurisdiction != nil {
					gotJ = *e.ProceedingType.Jurisdiction
				}
				t.Errorf("non-UPC event leaked: %s (jurisdiction=%q)", e.Code, gotJ)
			}
		}
	})

	t.Run("event_kind=filing narrows by kind", func(t *testing.T) {
		resp, err := svc.SearchEvents(ctx, "", EventSearchOptions{
			EventKind: "filing",
			Limit:     200,
		})
		if err != nil {
			t.Fatalf("search events filing: %v", err)
		}
		if len(resp.Events) == 0 {
			t.Fatalf("expected filing events, got 0")
		}
		for _, e := range resp.Events {
			if e.EventKind == nil || *e.EventKind != "filing" {
				gotKind := "<nil>"
				if e.EventKind != nil {
					gotKind = *e.EventKind
				}
				t.Errorf("non-filing event leaked: %s (event_kind=%q)", e.Code, gotKind)
			}
		}
	})
}

// TestLookupFollowUps covers the GET /api/tools/fristenrechner/follow-ups
// endpoint contract (overhaul S1, design §6.2). Verified against live
// data: looking up upc.inf.cfi.soc returns the four canonical follow-up
// rules (Klageerwiderung, CCR, Einspruch, Vertraulichkeits-Erwiderung),
// each with a computed due date or court-set marker.
func TestLookupFollowUps(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()

	holidays := NewHolidayService(pool)
	courts := NewCourtService(pool)
	rules := NewDeadlineRuleService(pool)
	fr := NewFristenrechnerService(rules, holidays, courts)

	t.Run("SoC returns follow-ups with computed dates", func(t *testing.T) {
		resp, err := fr.LookupFollowUps(ctx, "upc.inf.cfi.soc", "2026-05-20", "", "")
		if err != nil {
			t.Fatalf("lookup follow-ups: %v", err)
		}
		if resp.Trigger.Code != "upc.inf.cfi.soc" {
			t.Errorf("trigger code = %q, want upc.inf.cfi.soc", resp.Trigger.Code)
		}
		if len(resp.FollowUps) == 0 {
			t.Fatalf("expected follow-ups, got 0")
		}
		// At least the Klageerwiderung (sod) should be present and have a date.
		var sod *FollowUpRule
		for i := range resp.FollowUps {
			if resp.FollowUps[i].EventCode == "upc.inf.cfi.sod" {
				sod = &resp.FollowUps[i]
				break
			}
		}
		if sod == nil {
			t.Fatalf("Klageerwiderung (upc.inf.cfi.sod) not in follow-ups")
		}
		if sod.DueDate == "" {
			t.Errorf("expected due_date populated for sod, got empty")
		}
		if sod.Priority != "mandatory" {
			t.Errorf("expected priority=mandatory for sod, got %q", sod.Priority)
		}
		// 3 months after 2026-05-20 (then weekend-adjusted) — sanity check
		// only that something resembling 2026-08 came back.
		if len(sod.DueDate) < 7 || sod.DueDate[:7] != "2026-08" {
			t.Errorf("expected due_date in 2026-08, got %q", sod.DueDate)
		}
	})

	t.Run("party=defendant narrows but keeps bilateral rules", func(t *testing.T) {
		resp, err := fr.LookupFollowUps(ctx, "upc.inf.cfi.soc", "2026-05-20", "defendant", "")
		if err != nil {
			t.Fatalf("lookup follow-ups (defendant): %v", err)
		}
		if len(resp.FollowUps) == 0 {
			t.Fatalf("expected defendant follow-ups, got 0")
		}
		for _, r := range resp.FollowUps {
			if r.PrimaryParty == nil {
				continue
			}
			p := *r.PrimaryParty
			if p == "claimant" {
				t.Errorf("claimant-only rule leaked under defendant filter: %s", r.EventCode)
			}
		}
	})

	t.Run("unknown event returns ErrUnknownProceduralEvent", func(t *testing.T) {
		_, err := fr.LookupFollowUps(ctx, "no.such.event", "2026-05-20", "", "")
		if err != ErrUnknownProceduralEvent {
			t.Errorf("expected ErrUnknownProceduralEvent, got %v", err)
		}
	})
}
