package services

// Tests for the Slice 2 (project-subtree) sheet registry. Pure-function
// shape tests — live-DB integration coverage of the SQL itself stays in
// the existing query patterns the personal-scope tests already cover.

import (
	"strings"
	"testing"
	"time"

	"github.com/google/uuid"
)

// TestProjectSheetQueries_RegistryShape pins the sheet inventory + the
// design's §2 contract: every entity sheet binds rootID as $1, and the
// approval_policies sheet ships with all three sources (project +
// ancestor + partner_unit_default).
func TestProjectSheetQueries_RegistryShape(t *testing.T) {
	rootID := uuid.MustParse("61e3fb9e-29fb-44aa-867e-a89469e2cacb")
	qs := projectSheetQueries(rootID, false)

	wantSheets := []string{
		"projects",
		"project_teams",
		"project_partner_units",
		"deadlines",
		"appointments",
		"parties",
		"notes",
		"documents",
		"project_events",
		"approval_requests",
		"approval_policies",
		"checklist_instances",
		"partner_units",
		"partner_unit_members",
		"users_referenced",
		"system_audit_log_subset",
		"ref__proceeding_types",
		"ref__event_types",
		"ref__event_categories",
		"ref__deadline_rules",
		"ref__deadline_concepts",
		"ref__courts",
		"ref__countries",
		"ref__holidays",
	}
	gotSheets := []string{}
	for _, q := range qs {
		gotSheets = append(gotSheets, q.SheetName)
	}
	if len(gotSheets) != len(wantSheets) {
		t.Fatalf("sheet count = %d, want %d (got %v)", len(gotSheets), len(wantSheets), gotSheets)
	}
	for i, want := range wantSheets {
		if gotSheets[i] != want {
			t.Errorf("sheet[%d] = %q, want %q", i, gotSheets[i], want)
		}
	}

	// Every NON-reference sheet binds rootID as $1.
	for _, q := range qs {
		if strings.HasPrefix(q.SheetName, "ref__") {
			if len(q.Args) != 0 {
				t.Errorf("ref sheet %q has %d args, want 0", q.SheetName, len(q.Args))
			}
			continue
		}
		if len(q.Args) != 1 {
			t.Errorf("entity sheet %q has %d args, want 1", q.SheetName, len(q.Args))
			continue
		}
		if got, ok := q.Args[0].(uuid.UUID); !ok || got != rootID {
			t.Errorf("entity sheet %q first arg = %v, want rootID %v", q.SheetName, q.Args[0], rootID)
		}
	}
}

// TestProjectSheetQueries_ApprovalPoliciesTripleSource verifies that the
// approval_policies sheet's SQL carries all three source tags so an
// importer can reconstruct the effective gate (Q4 lock-in).
func TestProjectSheetQueries_ApprovalPoliciesTripleSource(t *testing.T) {
	qs := projectSheetQueries(uuid.New(), false)
	var found *sheetQuery
	for i := range qs {
		if qs[i].SheetName == "approval_policies" {
			found = &qs[i]
			break
		}
	}
	if found == nil {
		t.Fatal("approval_policies sheet missing from registry")
	}
	for _, src := range []string{
		`'project'::text AS source`,
		`'ancestor'::text AS source`,
		`'partner_unit_default'::text AS source`,
	} {
		if !strings.Contains(found.SQL, src) {
			t.Errorf("approval_policies SQL missing %q tag — Q4 triple-source attribution broken.\nSQL:\n%s",
				src, found.SQL)
		}
	}
}

// TestProjectSheetQueries_DirectOnlyNarrowsSubtree pins that direct_only=true
// produces a subtree subquery resolving to exactly the root (no LIKE-walk).
func TestProjectSheetQueries_DirectOnlyNarrowsSubtree(t *testing.T) {
	subtreeAll := projectSubtreeProjectIDsSQL(false)
	subtreeRoot := projectSubtreeProjectIDsSQL(true)

	if !strings.Contains(subtreeAll, `LIKE r.path`) {
		t.Errorf("default subtree SQL missing path-LIKE descendant walk:\n%s", subtreeAll)
	}
	if strings.Contains(subtreeRoot, `LIKE`) {
		t.Errorf("direct_only subtree SQL still has LIKE walk — should be root-only:\n%s", subtreeRoot)
	}
	if !strings.Contains(subtreeRoot, `$1::uuid`) {
		t.Errorf("direct_only subtree SQL missing $1::uuid root reference:\n%s", subtreeRoot)
	}
}

// TestProjectSheetQueries_NoPersonalSidecars guards against an accidental
// inclusion of personal sidecars (caldav config, views, pins, paliadin
// turns) in the project-scope export. These are per-user, not per-project,
// and don't belong in a matter handover.
func TestProjectSheetQueries_NoPersonalSidecars(t *testing.T) {
	qs := projectSheetQueries(uuid.New(), false)
	for _, q := range qs {
		switch q.SheetName {
		case "my_caldav_config", "my_views", "my_pinned_projects", "my_card_layouts", "my_paliadin_turns", "me":
			t.Errorf("project-scope export must not include personal sidecar sheet %q", q.SheetName)
		}
		// Also defence-in-depth on the SQL: no SELECT from
		// user_caldav_config or paliadin_turns from project scope.
		if strings.Contains(q.SQL, "user_caldav_config") {
			t.Errorf("sheet %q SQL touches user_caldav_config — never in project scope", q.SheetName)
		}
		if strings.Contains(q.SQL, "paliadin_turns") {
			t.Errorf("sheet %q SQL touches paliadin_turns — never in project scope", q.SheetName)
		}
	}
}

// TestProjectSheetQueries_AttachedPartnerUnitsOnly pins that the
// partner_units sheet is filtered to attached units only (not the full
// org chart).
func TestProjectSheetQueries_AttachedPartnerUnitsOnly(t *testing.T) {
	qs := projectSheetQueries(uuid.New(), false)
	for _, q := range qs {
		if q.SheetName != "partner_units" {
			continue
		}
		if !strings.Contains(q.SQL, "project_partner_units") {
			t.Errorf("partner_units sheet SQL must filter via project_partner_units (got attached-only requirement):\n%s",
				q.SQL)
		}
		return
	}
	t.Fatal("partner_units sheet missing from registry")
}

// TestShortUUIDSuffix_ReturnsLast8Hex pins the §3 filename disambiguator
// shape — Q5 lock-in.
func TestShortUUIDSuffix_ReturnsLast8Hex(t *testing.T) {
	cases := []struct {
		in   uuid.UUID
		want string
	}{
		{uuid.Nil, ""},
		{uuid.MustParse("11111111-1111-1111-1111-aaaaaaaaaaaa"), "aaaaaaaaaaaa"},
		{uuid.MustParse("61e3fb9e-29fb-44aa-867e-a89469e2cacb"), "a89469e2cacb"},
	}
	for _, c := range cases {
		got := shortUUIDSuffix(c.in)
		if got != c.want {
			t.Errorf("shortUUIDSuffix(%v) = %q, want %q", c.in, got, c.want)
		}
	}
}

// TestMetaToKeyValueRows_ProjectScopeRows verifies that project-scope
// meta picks up scope_root_label + scope_root_path + direct_only rows
// (so the __meta sheet carries Q6 lock-in details).
func TestMetaToKeyValueRows_ProjectScopeRows(t *testing.T) {
	rootID := uuid.MustParse("61e3fb9e-29fb-44aa-867e-a89469e2cacb")
	m := ExportMeta{
		SchemaVersion:  1,
		FirmName:       "HLC",
		Scope:          ExportScopeProject,
		ScopeRootID:    &rootID,
		ScopeRootLabel: "Siemens AG",
		ScopeRootPath:  "61e3fb9e_29fb_44aa_867e_a89469e2cacb",
		DirectOnly:     false,
		GeneratedAt:    time.Date(2026, 5, 20, 14, 23, 0, 0, time.UTC),
		RowCounts:      map[string]int{},
	}
	rows := metaToKeyValueRows(m)
	want := map[string]string{
		"scope_root_label": "Siemens AG",
		"scope_root_path":  "61e3fb9e_29fb_44aa_867e_a89469e2cacb",
		"direct_only":      "FALSE",
	}
	seen := map[string]string{}
	for _, r := range rows {
		seen[r[0]] = r[1]
	}
	for k, v := range want {
		if seen[k] != v {
			t.Errorf("meta key %q = %q, want %q (full rows: %v)", k, seen[k], v, rows)
		}
	}
}

