package services

// Live-DB integration test for the counterclaim sub-project shape
// (t-paliad-174 SmartTimeline Slice 3). Skipped without TEST_DATABASE_URL,
// matching the convention of the other live tests in this package.
//
// The test exercises the end-to-end shape:
//   1. CreateCounterclaim atomically creates child + flips our_side +
//      writes audit rows on parent AND child + sets counterclaim_of.
//   2. parent_id of the child equals parent's parent_id (sibling-under-
//      patent placement).
//   3. ProjectionService.For on the parent surfaces a parallel-track
//      counterclaim event; AvailableTracks lists the new track.
//   4. ProjectionService.For on the child surfaces the parent's events
//      with track="parent_context:<parent_id>".
//   5. Two-level CCR chains are rejected at the schema level.

import (
	"context"
	"errors"
	"os"
	"strings"
	"testing"

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

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

func TestCreateCounterclaim_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() // sibling parent: the patent hub
	caseID := uuid.New()   // the parent case (upc.inf.cfi)

	// Resolve upc.inf.cfi + upc.rev.cfi ids once. We need real ids from
	// the proceeding_types seed because they're NOT NULL on the test row.
	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)
	}

	cleanup := func() {
		// Delete CCR children first (FK to caseID via counterclaim_of is
		// ON DELETE SET NULL but the child rows still hold parent_id =
		// patentID — clear them via a parent_id sweep).
		pool.ExecContext(ctx, `DELETE FROM paliad.project_events WHERE project_id IN (
			SELECT id FROM paliad.projects WHERE counterclaim_of = $1)`, caseID)
		pool.ExecContext(ctx, `DELETE FROM paliad.project_teams WHERE project_id IN (
			SELECT id FROM paliad.projects WHERE counterclaim_of = $1)`, caseID)
		pool.ExecContext(ctx, `DELETE FROM paliad.projects        WHERE counterclaim_of = $1`, caseID)
		pool.ExecContext(ctx, `DELETE FROM paliad.project_events  WHERE project_id IN ($1, $2)`, caseID, patentID)
		pool.ExecContext(ctx, `DELETE FROM paliad.project_teams   WHERE project_id IN ($1, $2)`, caseID, patentID)
		pool.ExecContext(ctx, `DELETE FROM paliad.projects        WHERE id IN ($1, $2)`, caseID, 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, 'ccr-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, 'ccr-test@hlc.com', 'CCR Test', 'munich', 'global_admin', 'de')`,
		userID); err != nil {
		t.Fatalf("seed paliad.users: %v", err)
	}
	// Parent patent hub.
	if _, err := pool.ExecContext(ctx,
		`INSERT INTO paliad.projects (id, type, path, title, patent_number, status, created_by)
		 VALUES ($1, 'patent', $1::text, 'EP3456789 — Test Patent', 'EP3456789', '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)
	}
	// Child case (upc.inf.cfi) under the patent.
	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', 'active', $3, $4, 'claimant')`,
		caseID, patentID, userID, upcInf); err != nil {
		t.Fatalf("seed 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)`,
		caseID, userID); err != nil {
		t.Fatalf("seed case team: %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)

	t.Run("CreateCounterclaim flips our_side, places sibling, audits both", func(t *testing.T) {
		child, err := projects.CreateCounterclaim(ctx, userID, caseID, CounterclaimOpts{})
		if err != nil {
			t.Fatalf("CreateCounterclaim: %v", err)
		}
		defer pool.ExecContext(ctx, `DELETE FROM paliad.project_events WHERE project_id IN ($1, $2)`, child.ID, caseID)
		defer pool.ExecContext(ctx, `DELETE FROM paliad.project_teams WHERE project_id = $1`, child.ID)
		defer pool.ExecContext(ctx, `DELETE FROM paliad.projects WHERE id = $1`, child.ID)

		// 1. counterclaim_of points at the parent.
		if child.CounterclaimOf == nil || *child.CounterclaimOf != caseID {
			t.Errorf("child.CounterclaimOf = %v, want %v", child.CounterclaimOf, caseID)
		}
		// 2. parent_id = parent's parent_id = patent hub (sibling-under-patent).
		if child.ParentID == nil || *child.ParentID != patentID {
			t.Errorf("child.ParentID = %v, want %v (sibling under patent)", child.ParentID, patentID)
		}
		// 3. our_side flipped: parent claimant → child defendant.
		if child.OurSide == nil || *child.OurSide != "defendant" {
			t.Errorf("child.OurSide = %v, want defendant", child.OurSide)
		}
		// 4. Default proceeding_type_id resolved to upc.rev.cfi.
		if child.ProceedingTypeID == nil || *child.ProceedingTypeID != upcRev {
			t.Errorf("child.ProceedingTypeID = %v, want upc.rev.cfi (%d)", child.ProceedingTypeID, upcRev)
		}
		// 5. Auto-suggested title carries the patent reference + suffix.
		if !strings.Contains(child.Title, "EP3456789") || !strings.Contains(child.Title, "Widerklage") {
			t.Errorf("child.Title = %q, want it to contain EP3456789 and Widerklage", child.Title)
		}

		// 6. Audit rows on BOTH parent and child with timeline_kind='milestone'.
		var parentAudit, childAudit int
		if err := pool.GetContext(ctx, &parentAudit,
			`SELECT count(*) FROM paliad.project_events
			  WHERE project_id = $1 AND event_type = 'counterclaim_created'
			    AND timeline_kind = 'milestone'`, caseID); err != nil {
			t.Fatalf("count parent audit: %v", err)
		}
		if parentAudit != 1 {
			t.Errorf("parent counterclaim_created rows = %d, want 1", parentAudit)
		}
		if err := pool.GetContext(ctx, &childAudit,
			`SELECT count(*) FROM paliad.project_events
			  WHERE project_id = $1 AND event_type = 'counterclaim_created'
			    AND timeline_kind = 'milestone'`, child.ID); err != nil {
			t.Fatalf("count child audit: %v", err)
		}
		if childAudit != 1 {
			t.Errorf("child counterclaim_created rows = %d, want 1", childAudit)
		}
	})

	t.Run("ProjectionService.For on parent surfaces counterclaim track", func(t *testing.T) {
		child, err := projects.CreateCounterclaim(ctx, userID, caseID, CounterclaimOpts{})
		if err != nil {
			t.Fatalf("CreateCounterclaim: %v", err)
		}
		defer pool.ExecContext(ctx, `DELETE FROM paliad.project_events WHERE project_id IN ($1, $2)`, child.ID, caseID)
		defer pool.ExecContext(ctx, `DELETE FROM paliad.project_teams WHERE project_id = $1`, child.ID)
		defer pool.ExecContext(ctx, `DELETE FROM paliad.projects WHERE id = $1`, child.ID)

		rows, meta, err := projection.For(ctx, userID, caseID, ProjectionOpts{})
		if err != nil {
			t.Fatalf("projection.For parent: %v", err)
		}
		// AvailableTracks contains parent + the new counterclaim track.
		expectTrack := "counterclaim:" + child.ID.String()
		var sawCounterclaimTrack bool
		for _, t := range meta.AvailableTracks {
			if t == expectTrack {
				sawCounterclaimTrack = true
				break
			}
		}
		if !sawCounterclaimTrack {
			t.Errorf("AvailableTracks = %v, want to contain %q", meta.AvailableTracks, expectTrack)
		}

		// At least one row carries the counterclaim track + the
		// SubProjectID = child.ID.
		var countCCR int
		for _, r := range rows {
			if r.Track == expectTrack {
				countCCR++
				if r.SubProjectID == nil || *r.SubProjectID != child.ID {
					t.Errorf("ccr-track row missing SubProjectID = child.ID")
				}
			}
		}
		if countCCR == 0 {
			t.Errorf("expected at least one row on counterclaim track, saw 0 (rows=%d)", len(rows))
		}
	})

	t.Run("ProjectionService.For on child surfaces parent_context track", func(t *testing.T) {
		child, err := projects.CreateCounterclaim(ctx, userID, caseID, CounterclaimOpts{})
		if err != nil {
			t.Fatalf("CreateCounterclaim: %v", err)
		}
		defer pool.ExecContext(ctx, `DELETE FROM paliad.project_events WHERE project_id IN ($1, $2)`, child.ID, caseID)
		defer pool.ExecContext(ctx, `DELETE FROM paliad.project_teams WHERE project_id = $1`, child.ID)
		defer pool.ExecContext(ctx, `DELETE FROM paliad.projects WHERE id = $1`, child.ID)

		rows, meta, err := projection.For(ctx, userID, child.ID, ProjectionOpts{})
		if err != nil {
			t.Fatalf("projection.For child: %v", err)
		}
		expectTrack := "parent_context:" + caseID.String()
		var sawParentContext bool
		for _, t := range meta.AvailableTracks {
			if t == expectTrack {
				sawParentContext = true
				break
			}
		}
		if !sawParentContext {
			t.Errorf("AvailableTracks = %v, want to contain %q", meta.AvailableTracks, expectTrack)
		}
		var countParentCtx int
		for _, r := range rows {
			if r.Track == expectTrack {
				countParentCtx++
				if r.SubProjectID == nil || *r.SubProjectID != caseID {
					t.Errorf("parent_context row missing SubProjectID = parent.ID")
				}
			}
		}
		if countParentCtx == 0 {
			t.Errorf("expected at least one parent_context row, saw 0 (rows=%d)", len(rows))
		}
	})

	t.Run("Two-level CCR chains are rejected at the schema level", func(t *testing.T) {
		child, err := projects.CreateCounterclaim(ctx, userID, caseID, CounterclaimOpts{})
		if err != nil {
			t.Fatalf("CreateCounterclaim: %v", err)
		}
		defer pool.ExecContext(ctx, `DELETE FROM paliad.project_events WHERE project_id IN ($1, $2)`, child.ID, caseID)
		defer pool.ExecContext(ctx, `DELETE FROM paliad.project_teams WHERE project_id = $1`, child.ID)
		defer pool.ExecContext(ctx, `DELETE FROM paliad.projects WHERE id = $1`, child.ID)

		// Trying to create a CCR against the CCR child = two-level chain.
		// CreateCounterclaim guards with an early ErrInvalidInput before
		// hitting the trigger; verify the early guard fires.
		_, err = projects.CreateCounterclaim(ctx, userID, child.ID, CounterclaimOpts{})
		if err == nil {
			t.Fatal("expected error for two-level CCR chain, got nil")
		}
		if !errors.Is(err, ErrInvalidInput) {
			t.Errorf("expected ErrInvalidInput, got %v", err)
		}

		// Also pin the schema-level trigger guard: a direct INSERT
		// pointing at a row that already has counterclaim_of NOT NULL
		// must be rejected by paliad.projects_no_two_level_ccr.
		grandchild := uuid.New()
		_, err = pool.ExecContext(ctx,
			`INSERT INTO paliad.projects
			   (id, type, parent_id, path, title, status, created_by, counterclaim_of)
			 VALUES ($1, 'case', $2, $1::text, 'Grandchild CCR', 'active', $3, $4)`,
			grandchild, patentID, userID, child.ID)
		if err == nil {
			pool.ExecContext(ctx, `DELETE FROM paliad.projects WHERE id = $1`, grandchild)
			t.Fatal("expected schema trigger to reject grandchild CCR insert, got success")
		}
		if !strings.Contains(err.Error(), "two-level counterclaim") {
			t.Errorf("trigger error message: %v (want two-level counterclaim)", err)
		}
	})
}
