package services

import (
	"context"
	"os"
	"regexp"
	"testing"

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

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

// submissionCodeShapeRegex is the proceeding-code-prefixed shape
// installed by mig 098 (t-paliad-209): the proceeding's 3-segment code
// (`^[a-z_0-9]+\.[a-z_0-9]+\.[a-z_0-9]+\.`) followed by at least one
// suffix segment (and optional further dot-separated segments). The
// regex allows digits so EPA suffixes like `r106` / `r71_3` / `r116`
// (statutory rule numbers in the suffix) pass alongside canonical
// dotted-word codes. Underscores cover the legacy archived bucket
// (`_archived_…`) and hand-seeded test rules. Mirrors the assertion in
// mig 098 §6.1.
var submissionCodeShapeRegex = regexp.MustCompile(
	`^[a-z_0-9]+\.[a-z_0-9]+\.[a-z_0-9]+\.[a-z_0-9]+(\..*)?$`)

// TestSubmissionCodeShape walks every active+published row in
// paliad.deadline_rules and asserts that submission_code matches the
// 4+-segment proceeding-code-prefixed shape ratified for t-paliad-209.
// Sibling of TestProceedingCodeShape — same pattern, same goal: catch
// drift between the migration's hard invariant and runtime state.
//
// Archived rows (proceeding `_archived_litigation`) are exempted; mig
// 098's §6.1 assertion does the same by gating on lifecycle_state =
// 'published'. Their codes get the archived prefix and the wider shape
// they end up with sits outside the 4+-segment canonical form by
// design.
//
// Skipped when TEST_DATABASE_URL is unset, mirroring the pattern in
// proceeding_codes_shape_test.go.
func TestSubmissionCodeShape(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()

	var rows []struct {
		ID             string  `db:"id"`
		SubmissionCode *string `db:"submission_code"`
	}
	if err := pool.SelectContext(ctx, &rows,
		`SELECT dr.id::text AS id, dr.submission_code
		   FROM paliad.deadline_rules dr
		   JOIN paliad.proceeding_types pt ON pt.id = dr.proceeding_type_id
		  WHERE dr.is_active = true
		    AND dr.lifecycle_state = 'published'
		    AND pt.category = 'fristenrechner'
		  ORDER BY dr.id`); err != nil {
		t.Fatalf("load active+published deadline_rules rows: %v", err)
	}
	if len(rows) == 0 {
		t.Fatal("no active+published fristenrechner deadline_rules — mig 098 likely not applied")
	}
	for _, r := range rows {
		if r.SubmissionCode == nil {
			t.Errorf("deadline_rules[id=%s] submission_code is NULL", r.ID)
			continue
		}
		if !submissionCodeShapeRegex.MatchString(*r.SubmissionCode) {
			t.Errorf("deadline_rules[id=%s] submission_code=%q does not match shape %s",
				r.ID, *r.SubmissionCode, submissionCodeShapeRegex.String())
		}
	}
}

// TestSubmissionCodeShapeRegexStandalone exercises the regex without a
// DB so the shape rule is verified on every `go test ./...` run.
func TestSubmissionCodeShapeRegexStandalone(t *testing.T) {
	good := []string{
		"upc.inf.cfi.soc",
		"upc.inf.cfi.sod",
		"upc.inf.cfi.def_to_ccr",
		"upc.rev.cfi.app",
		"de.inf.lg.klage",
		"de.inf.bgh.revision",
		"de.null.bgh.berufung",
		"dpma.appeal.bpatg.begruendung",
		"epa.opp.opd.beschwerde_begr",
	}
	for _, code := range good {
		if !submissionCodeShapeRegex.MatchString(code) {
			t.Errorf("good code %q rejected by submission-code shape regex", code)
		}
	}
	bad := []string{
		"inf.soc",        // pre-mig-098: 2 segments
		"upc.inf",        // 2 segments
		"upc.inf.cfi",    // proceeding code shape, not a submission code
		"UPC.INF.CFI.SOC", // uppercase
		"upc-inf-cfi-soc", // dashes
		"",
	}
	for _, code := range bad {
		if submissionCodeShapeRegex.MatchString(code) {
			t.Errorf("bad code %q accepted by submission-code shape regex", code)
		}
	}
}
