package services

import (
	"context"
	"os"
	"sort"
	"testing"

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

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

// TestEventCategoryCoverage is the v4 (t-paliad-136 Phase C) replacement
// for migration 049's `RAISE EXCEPTION` coverage gate. m's Q7 (2026-05-05)
// moved gating from migration time (which can block server boot — last
// night's outage was caused by exactly that) to CI time.
//
// Invariant: every concept with category='submission' and is_active=true
// MUST be reachable from at least one row in paliad.event_category_concepts,
// EXCEPT a small exempt list of pure-administrative concepts that live on
// Pathway A (browse-by-proceeding) only — they don't fit the "what just
// happened" mental model that drives Pathway B.
//
// Why this matters: an unreachable submission concept is invisible from
// the decision tree. The user can't pick it via "CMS-Eingang → …" or any
// other narrative entry — they'd have to know the German legal name and
// type it into B2 search. That's an acceptable fallback for prosecution-
// only artefacts (filing, exam request, translation), and not for
// litigation submissions.
//
// Skipped when TEST_DATABASE_URL is unset, mirroring the other live-DB
// tests in this package. CI must export TEST_DATABASE_URL pointing at a
// fresh paliad-schema DB; the migration runner brings it up to head.
func TestEventCategoryCoverage(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()

	// Pathway-A-only exempt list. After Phase C (migration 052), reply-
	// to-cross-appeal is reachable via the upc-{inf,rev}.berufungsschrift
	// leaves and the new ich-moechte-einreichen.spaetere-schriftsaetze.
	// reply-to-cross-appeal-upc leaf, so it's no longer exempt here.
	exempt := map[string]bool{
		"filing":                   true, // filed during prosecution
		"request-for-examination":  true, // EPC R.70(1), prosecution
		"approval-and-translation": true, // EPC R.71(3), prosecution
	}

	ctx := context.Background()
	rows, err := pool.QueryxContext(ctx, `
		SELECT dc.slug
		  FROM paliad.deadline_concepts dc
		 WHERE dc.is_active
		   AND dc.category = 'submission'
		   AND NOT EXISTS (
		         SELECT 1
		           FROM paliad.event_category_concepts ecc
		          WHERE ecc.concept_id = dc.id
		       )
		 ORDER BY dc.slug
	`)
	if err != nil {
		t.Fatalf("coverage query: %v", err)
	}
	defer rows.Close()

	var unreachable []string
	for rows.Next() {
		var slug string
		if err := rows.Scan(&slug); err != nil {
			t.Fatalf("scan: %v", err)
		}
		if exempt[slug] {
			continue
		}
		unreachable = append(unreachable, slug)
	}
	if err := rows.Err(); err != nil {
		t.Fatalf("rows.Err: %v", err)
	}

	if len(unreachable) > 0 {
		sort.Strings(unreachable)
		t.Errorf("v4 coverage gate: %d submission concept(s) unreachable from any event_category leaf:\n  %v\n"+
			"Either map them via paliad.event_category_concepts, or add to the exempt list with a comment "+
			"explaining why they live on Pathway A only.",
			len(unreachable), unreachable)
	}
}
