package bootstrap

import (
	"strings"
	"testing"
)

// TestAuditOutboxTableC2 asserts _System_AuditOutbox exists in system_tables.json
// with columns and indices per design doc §2.4. The table is added by C2 and
// serves as the async field-diff fan-out queue. No consumers yet (outbox writer
// lands in C5, worker in C6).
func TestAuditOutboxTableC2(t *testing.T) {
	defs := GetSystemTableDefinitions()

	type colShape struct {
		colType       string
		nullable      bool
		primaryKey    bool
		autoIncrement 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_AuditOutbox" {
			continue
		}
		found = true
		for _, c := range d.Columns {
			columns[c.Name] = colShape{
				colType:       c.Type,
				nullable:      c.Nullable,
				primaryKey:    c.PrimaryKey,
				autoIncrement: c.AutoIncrement,
				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_AuditOutbox not found in system_tables.json (C2 §2.4)")
	}

	// Columns per §2.4.
	expected := []struct {
		name     string
		colType  string
		nullable bool
	}{
		{"Id", "BIGINT", false},
		{"Payload", "JSON", false},
		{"EnqueuedAt", "DATETIME(6)", false},
		{"ProcessedAt", "DATETIME(6)", true},
		{"Attempts", "INT", false},
		{"LeaseHolderId", "VARCHAR(64)", true},
		{"LeaseExpiresAt", "DATETIME(6)", true},
		{"LastError", "VARCHAR(1024)", true},
		{"PoisonClass", "VARCHAR(32)", true},
	}

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

	// Id must be primary key + autoincrement (§2.4 "BIGINT AUTO_INCREMENT PK").
	if idCol, ok := columns["Id"]; ok {
		if !idCol.primaryKey {
			t.Errorf("_System_AuditOutbox.Id must be primaryKey=true")
		}
		if !idCol.autoIncrement {
			t.Errorf("_System_AuditOutbox.Id must be autoIncrement=true")
		}
	}

	// Attempts must default to 0 per §2.4.
	if att, ok := columns["Attempts"]; ok {
		if !att.hasDefault || strings.TrimSpace(att.defaultValue) != "0" {
			t.Errorf("_System_AuditOutbox.Attempts: default = %q, want %q", att.defaultValue, "0")
		}
	}

	// Indices per §2.4:
	//   idx_unprocessed (ProcessedAt, PoisonClass, EnqueuedAt)
	//   idx_lease       (LeaseHolderId, LeaseExpiresAt)
	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{"ProcessedAt", "PoisonClass", "EnqueuedAt"}) {
		t.Errorf("_System_AuditOutbox missing index (ProcessedAt, PoisonClass, EnqueuedAt) (§2.4)")
	}
	if !mustHaveIndex([]string{"LeaseHolderId", "LeaseExpiresAt"}) {
		t.Errorf("_System_AuditOutbox missing index (LeaseHolderId, LeaseExpiresAt) (§2.4)")
	}
}

// TestAuditOutboxDoesNotMutateC1 asserts C2 did not alter _System_AuditLog's
// C1 column shape. The uk_outbox UNIQUE KEY on _System_AuditLog is a separate
// concern flagged for user decision (see C2 report).
func TestAuditOutboxDoesNotMutateC1(t *testing.T) {
	defs := GetSystemTableDefinitions()
	for _, d := range defs {
		if d.TableName != "_System_AuditLog" {
			continue
		}
		for _, c := range d.Columns {
			if c.Name == "ChangesJSON" {
				if c.Type != "LONGTEXT" || !c.Nullable {
					t.Errorf("_System_AuditLog.ChangesJSON shape altered by C2 (want LONGTEXT NULL, got %q nullable=%v)", c.Type, c.Nullable)
				}
			}
			if c.Name == "Id" && !c.PrimaryKey {
				t.Errorf("_System_AuditLog.Id lost primaryKey — C2 must not touch PK")
			}
		}
		return
	}
	t.Fatalf("_System_AuditLog not found — prior-commit regression")
}
