package bootstrap

import (
	"strings"
	"testing"
)

// TestRequestCaptureTableC13 asserts _System_RequestCapture exists in
// system_tables.json with the shape specified by design §14.2. The table is a
// new L1 seed added by C13; no writer in this commit (middleware lands in
// C14). Rows are keyed by RequestId (UUID), joined logically to
// _System_AuditLog via the same column.
//
// Deviations from §14.2 text (explicitly intentional, documented here so a
// future reviewer sees the tradeoff):
//
//   - Body / ResponseBody: design specifies LONGBLOB for binary-upload
//     compatibility. The C13 DDL validator (schema_types.go) does not yet
//     accept LONGBLOB; LONGTEXT is used as a pragmatic substitute. Bodies
//     stored in this table are ALWAYS scrubbed (§14.3) before insert, so the
//     scrub-output is text (original binary bytes are replaced with a
//     "***BINARY_UNPARSEABLE_SCRUBBED***" sentinel per §14.3). Upgrading to
//     LONGBLOB + a validator rule is deferred; no row-data loss risk since
//     the middleware doesn't exist yet.
//   - Primary key: §14.2 specifies composite PK (RequestId, ReceivedAt) for
//     TiDB partition-key constraint. The current schema-builder emits inline
//     `PRIMARY KEY` per-column (see persistence/schema_ddl.go buildColumnDDL
//     lines 52-54); a composite PK would require a builder change. Because
//     RequestId is a UUID (globally unique), a single-column PK on RequestId
//     is semantically sufficient for C13 (no partitioning is applied either).
//     Upgrading to composite PK + PARTITION BY RANGE is Phase 2B work, not
//     C13 scope.
//   - ResponseStatus: INT instead of SMALLINT. Both are in the physical-type
//     allowlist; INT is more commonly used in the codebase.
func TestRequestCaptureTableC13(t *testing.T) {
	defs := GetSystemTableDefinitions()

	type colShape struct {
		colType      string
		nullable     bool
		primaryKey   bool
		hasDefault   bool
		defaultValue string
	}

	var (
		found   bool
		columns = map[string]colShape{}
		indices []struct {
			name    string
			columns []string
			unique  bool
		}
	)

	for _, d := range defs {
		if d.TableName != "_System_RequestCapture" {
			continue
		}
		found = true
		for _, c := range d.Columns {
			columns[c.Name] = colShape{
				colType:      c.Type,
				nullable:     c.Nullable,
				primaryKey:   c.PrimaryKey,
				hasDefault:   c.Default != "",
				defaultValue: c.Default,
			}
		}
		for _, idx := range d.Indices {
			indices = append(indices, struct {
				name    string
				columns []string
				unique  bool
			}{name: idx.Name, columns: idx.Columns, unique: idx.Unique})
		}
		break
	}

	if !found {
		t.Fatalf("_System_RequestCapture not found in system_tables.json (C13 §14.2)")
	}

	// Columns per §14.2 (with explicit deviations listed in doc-comment).
	expected := []struct {
		name     string
		colType  string
		nullable bool
	}{
		{"RequestId", "VARCHAR(64)", false},
		{"ReceivedAt", "DATETIME(6)", false},
		{"Method", "VARCHAR(10)", false},
		{"Path", "VARCHAR(512)", false},
		{"Query", "LONGTEXT", true},
		{"Headers", "JSON", true},
		{"Body", "LONGTEXT", true},
		{"BodySize", "INT", false},
		{"BodyTruncated", "BOOLEAN", false},
		{"ResponseStatus", "INT", false},
		{"ResponseBody", "LONGTEXT", true},
		{"UserId", "VARCHAR(255)", true},
		{"SessionId", "VARCHAR(64)", true},
		{"ClientIp", "VARCHAR(45)", true},
		{"Entrypoint", "VARCHAR(32)", false},
		{"LatencyMs", "INT", false},
	}

	for _, e := range expected {
		got, ok := columns[e.name]
		if !ok {
			t.Errorf("_System_RequestCapture missing column %q (§14.2)", e.name)
			continue
		}
		if got.colType != e.colType {
			t.Errorf("_System_RequestCapture.%s: type = %q, want %q", e.name, got.colType, e.colType)
		}
		if got.nullable != e.nullable {
			t.Errorf("_System_RequestCapture.%s: nullable = %v, want %v", e.name, got.nullable, e.nullable)
		}
	}

	// RequestId must be primaryKey (see doc-comment for composite-PK deviation).
	if req, ok := columns["RequestId"]; ok && !req.primaryKey {
		t.Errorf("_System_RequestCapture.RequestId must be primaryKey=true")
	}

	// BodyTruncated must default to FALSE/0 per §14.2.
	if bt, ok := columns["BodyTruncated"]; ok {
		if !bt.hasDefault {
			t.Errorf("_System_RequestCapture.BodyTruncated must have a default")
		} else {
			d := strings.ToUpper(strings.TrimSpace(bt.defaultValue))
			if d != "FALSE" && d != "0" {
				t.Errorf("_System_RequestCapture.BodyTruncated: default = %q, want FALSE or 0", bt.defaultValue)
			}
		}
	}

	// Indices per §14.2:
	//   idx_time (ReceivedAt)
	//   idx_user (UserId, ReceivedAt)
	//   idx_path (Path, ReceivedAt) — no prefix-index in schema JSON format;
	//     builder will produce KEY `idx_path` (`Path`,`ReceivedAt`). TiDB
	//     accepts this without an explicit prefix.
	mustHaveIndex := func(wantCols []string) bool {
		for _, idx := range indices {
			if len(idx.columns) != len(wantCols) {
				continue
			}
			match := true
			for i, c := range idx.columns {
				if !strings.EqualFold(c, wantCols[i]) {
					match = false
					break
				}
			}
			if match {
				return true
			}
		}
		return false
	}

	if !mustHaveIndex([]string{"ReceivedAt"}) {
		t.Errorf("_System_RequestCapture missing index (ReceivedAt) (§14.2 idx_time)")
	}
	if !mustHaveIndex([]string{"UserId", "ReceivedAt"}) {
		t.Errorf("_System_RequestCapture missing index (UserId, ReceivedAt) (§14.2 idx_user)")
	}
	if !mustHaveIndex([]string{"Path", "ReceivedAt"}) {
		t.Errorf("_System_RequestCapture missing index (Path, ReceivedAt) (§14.2 idx_path)")
	}
}

// TestRequestCaptureTableC13_Category asserts the new table is registered
// under the "audit" category so discovery/reporting groups it alongside
// _System_AuditLog and _System_AuditOutbox.
func TestRequestCaptureTableC13_Category(t *testing.T) {
	defs := GetSystemTableDefinitions()
	for _, d := range defs {
		if d.TableName != "_System_RequestCapture" {
			continue
		}
		if d.Category != "audit" {
			t.Errorf("_System_RequestCapture.category = %q, want %q", d.Category, "audit")
		}
		if d.TableType != "system_core" {
			t.Errorf("_System_RequestCapture.tableType = %q, want %q", d.TableType, "system_core")
		}
		if d.Description == "" {
			t.Errorf("_System_RequestCapture.description must not be empty")
		}
		return
	}
	t.Fatalf("_System_RequestCapture not found")
}
