package services

// Live-DB integration test for the CCR-anchors-inf-rule path
// (t-paliad-237). The SmartTimeline on a CCR project surfaces parent
// inf rules in the parent_context lane; clicking "Datum setzen" on
// those rows used to bubble up as a generic 500 because the anchor
// lookup was scoped to the CCR's own proceeding_type_id. The service
// now detects the cross-proceeding case and rejects with a structured
// error pointing at the parent project — verified end-to-end here.

import (
	"context"
	"errors"
	"os"
	"testing"
	"time"

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

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

func TestRecordAnchor_CrossProceeding_RejectsWithParentPointer_Live(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()
	userID := uuid.New()
	patentID := uuid.New()
	parentCaseID := uuid.New()

	// Resolve upc.inf.cfi + upc.rev.cfi ids.
	var upcInf, upcRev int
	if err := pool.GetContext(ctx, &upcInf,
		`SELECT id FROM paliad.proceeding_types WHERE code = $1`,
		CodeUPCInfringement); err != nil {
		t.Fatalf("resolve %s: %v", CodeUPCInfringement, err)
	}
	if err := pool.GetContext(ctx, &upcRev,
		`SELECT id FROM paliad.proceeding_types WHERE code = $1`,
		CodeUPCRevocation); err != nil {
		t.Fatalf("resolve %s: %v", CodeUPCRevocation, err)
	}

	// Seed a unique inf-only rule for this test so the assertion does
	// not couple to the live seed corpus.
	infRuleID := uuid.New()
	infRuleCode := "test.t237.infonly." + uuid.NewString()[:8]

	cleanup := func() {
		pool.ExecContext(ctx, `DELETE FROM paliad.deadline_rules WHERE id = $1`, infRuleID)
		pool.ExecContext(ctx, `DELETE FROM paliad.project_events WHERE project_id IN (
			SELECT id FROM paliad.projects WHERE counterclaim_of = $1)`, parentCaseID)
		pool.ExecContext(ctx, `DELETE FROM paliad.project_teams WHERE project_id IN (
			SELECT id FROM paliad.projects WHERE counterclaim_of = $1)`, parentCaseID)
		pool.ExecContext(ctx, `DELETE FROM paliad.projects        WHERE counterclaim_of = $1`, parentCaseID)
		pool.ExecContext(ctx, `DELETE FROM paliad.project_events  WHERE project_id IN ($1, $2)`, parentCaseID, patentID)
		pool.ExecContext(ctx, `DELETE FROM paliad.project_teams   WHERE project_id IN ($1, $2)`, parentCaseID, patentID)
		pool.ExecContext(ctx, `DELETE FROM paliad.projects        WHERE id IN ($1, $2)`, parentCaseID, patentID)
		pool.ExecContext(ctx, `DELETE FROM paliad.users           WHERE id = $1`, userID)
		pool.ExecContext(ctx, `DELETE FROM auth.users             WHERE id = $1`, userID)
	}
	cleanup()
	defer cleanup()

	if _, err := pool.ExecContext(ctx,
		`INSERT INTO auth.users (id, email) VALUES ($1, 'cross-anchor-test@hlc.com')`,
		userID); err != nil {
		t.Fatalf("seed auth.users: %v", err)
	}
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.users (id, email, display_name, office, global_role, lang)
		 VALUES ($1, 'cross-anchor-test@hlc.com', 'Cross-Anchor Test', 'munich', 'global_admin', 'de')`,
		userID); err != nil {
		t.Fatalf("seed paliad.users: %v", err)
	}
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.projects (id, type, path, title, patent_number, status, created_by)
		 VALUES ($1, 'patent', $1::text, 'EP-T237 — Test Patent', 'EP-T237', 'active', $2)`,
		patentID, userID); err != nil {
		t.Fatalf("seed patent: %v", err)
	}
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.project_teams (project_id, user_id, role, responsibility, inherited, added_by)
		 VALUES ($1, $2, 'lead', 'lead', false, $2)`,
		patentID, userID); err != nil {
		t.Fatalf("seed patent team: %v", err)
	}
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.projects
		   (id, type, parent_id, path, title, status, created_by,
		    proceeding_type_id, our_side)
		 VALUES ($1, 'case', $2, $2::text || '.' || $1::text,
		         'UPC-CFI München — Klage (T237)', 'active', $3, $4, 'claimant')`,
		parentCaseID, patentID, userID, upcInf); err != nil {
		t.Fatalf("seed parent case: %v", err)
	}
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.project_teams (project_id, user_id, role, responsibility, inherited, added_by)
		 VALUES ($1, $2, 'lead', 'lead', false, $2)`,
		parentCaseID, userID); err != nil {
		t.Fatalf("seed parent team: %v", err)
	}

	// Inf-only rule. Lives under upc.inf.cfi (upcInf), NOT upc.rev.cfi.
	if _, err := pool.ExecContext(ctx,
		`SELECT set_config('paliad.audit_reason', $1, true)`,
		"t-paliad-237 cross-anchor test seed"); err != nil {
		t.Fatalf("set audit_reason: %v", err)
	}
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.deadline_rules
		   (id, proceeding_type_id, submission_code, name, name_en,
		    duration_value, duration_unit, timing, is_court_set, is_spawn,
		    sequence_order, is_active, priority,
		    lifecycle_state, created_at, updated_at)
		 VALUES ($1, $2, $3, 'Klageschrift (T237)', 'Statement of Claim (T237)',
		         0, 'days', 'after', false, false,
		         0, true, 'mandatory', 'published', now(), now())`,
		infRuleID, upcInf, infRuleCode); err != nil {
		t.Fatalf("seed inf rule: %v", err)
	}

	users := NewUserService(pool)
	projects := NewProjectService(pool, users)
	eventTypes := NewEventTypeService(pool, users)
	deadlines := NewDeadlineService(pool, projects, eventTypes)
	appointments := NewAppointmentService(pool, projects)
	rules := NewDeadlineRuleService(pool)
	holidays := NewHolidayService(pool)
	courts := NewCourtService(pool)
	fristen := NewFristenrechnerService(rules, holidays, courts)
	projection := NewProjectionService(pool, projects, deadlines, appointments, fristen, rules)

	// Create the CCR child. Default proceeding_type → upc.rev.cfi.
	child, err := projects.CreateCounterclaim(ctx, userID, parentCaseID, CounterclaimOpts{})
	if err != nil {
		t.Fatalf("CreateCounterclaim: %v", err)
	}
	if child.ProceedingTypeID == nil || *child.ProceedingTypeID != upcRev {
		t.Fatalf("CCR child proceeding_type_id = %v, want upcRev (%d)", child.ProceedingTypeID, upcRev)
	}

	// Anchor attempt #1: rule lives in the parent's proceeding type, not
	// the CCR's. Must reject with CrossProceedingAnchorError.
	_, err = projection.RecordAnchor(ctx, userID, child.ID, AnchorInput{
		RuleCode:   infRuleCode,
		ActualDate: time.Date(2026, 5, 22, 0, 0, 0, 0, time.UTC),
	})
	if err == nil {
		t.Fatal("expected cross-proceeding rejection, got nil")
	}
	cpe, ok := IsCrossProceedingAnchor(err)
	if !ok {
		t.Fatalf("expected CrossProceedingAnchorError, got %T: %v", err, err)
	}
	if cpe.RequestedRuleCode != infRuleCode {
		t.Errorf("RequestedRuleCode = %q, want %q", cpe.RequestedRuleCode, infRuleCode)
	}
	if cpe.ParentProjectID != parentCaseID {
		t.Errorf("ParentProjectID = %v, want %v", cpe.ParentProjectID, parentCaseID)
	}
	if cpe.RequestedRuleNameDE != "Klageschrift (T237)" {
		t.Errorf("RequestedRuleNameDE = %q", cpe.RequestedRuleNameDE)
	}
	if cpe.RequestedRuleNameEN != "Statement of Claim (T237)" {
		t.Errorf("RequestedRuleNameEN = %q", cpe.RequestedRuleNameEN)
	}

	// Anchor attempt #2: same rule code, anchored on the PARENT inf
	// project. Must succeed — the legitimate happy path stays intact.
	res, err := projection.RecordAnchor(ctx, userID, parentCaseID, AnchorInput{
		RuleCode:   infRuleCode,
		ActualDate: time.Date(2026, 5, 22, 0, 0, 0, 0, time.UTC),
	})
	if err != nil {
		t.Fatalf("anchor on parent inf project failed: %v", err)
	}
	if res == nil || res.DeadlineID == nil {
		t.Fatalf("expected DeadlineID set on anchor result, got %+v", res)
	}

	// Anchor attempt #3: rule code that exists in NEITHER project — must
	// still return the legacy "unknown submission_code" error, not the
	// cross-proceeding error.
	_, err = projection.RecordAnchor(ctx, userID, child.ID, AnchorInput{
		RuleCode:   "test.t237.nonexistent." + uuid.NewString()[:8],
		ActualDate: time.Date(2026, 5, 22, 0, 0, 0, 0, time.UTC),
	})
	if err == nil {
		t.Fatal("expected error for unknown rule, got nil")
	}
	if _, ok := IsCrossProceedingAnchor(err); ok {
		t.Errorf("unknown rule should NOT surface as cross-proceeding error: %v", err)
	}
	if !errors.Is(err, ErrInvalidInput) {
		t.Errorf("unknown rule should wrap ErrInvalidInput, got %v", err)
	}
}
