package bootstrapimpl

import (
	"strings"
	"testing"
)

// TestConnectorInboundRouteTableExists asserts that _System_ConnectorInboundRoute is
// declared in system_tables.json and carries the expected key columns.
// Mirrors system_tables_connector_rate_limit_test.go pattern.
func TestConnectorInboundRouteTableExists(t *testing.T) {
	const tableName = "_System_ConnectorInboundRoute"
	defs := GetManagedTableDefinitions()

	var found bool
	for _, def := range defs {
		if def.TableName != tableName {
			continue
		}
		found = true

		wantCols := []string{
			"Id", "Namespace", "InstalledPackId",
			"EventKind", "FlowDeveloperName", "InvocationMode", "BatchInputName",
			"BatchResultOutputName", "MaxBatchSize", "QueueClass", "FlowExecutionFilter", "IsDeleted",
			"CreatedDate", "LastModifiedDate",
		}
		colIdx := make(map[string]bool, len(def.Columns))
		for _, col := range def.Columns {
			colIdx[col.Name] = true
		}
		for _, name := range wantCols {
			if !colIdx[name] {
				t.Errorf("%s: missing expected column %q", tableName, name)
			}
		}
		break
	}

	if !found {
		t.Fatalf("%s not found in system_tables.json", tableName)
	}
}

// TestConnectorInboundRouteRequiredWritePermissionIsBlocked pins requiredWritePermission
// to "BLOCKED". Direct writes would bypass connector pack validation and could inject
// arbitrary routing rules outside the pack-install pipeline.
// Per contract §6 table schema and §8 AC.
func TestConnectorInboundRouteRequiredWritePermissionIsBlocked(t *testing.T) {
	const (
		tableName = "_System_ConnectorInboundRoute"
		wantPerm  = "BLOCKED"
	)
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != tableName {
			continue
		}
		if def.RequiredWritePermission != wantPerm {
			t.Fatalf(
				"%s.requiredWritePermission = %q, want %q; "+
					"direct writes bypass connector pack validation (NCRM-INT-11b). "+
					"Write path is ConnectorInboundRouteComponent.Apply only.",
				tableName, def.RequiredWritePermission, wantPerm,
			)
		}
		return
	}
	t.Fatalf("%s not found in system_tables.json", tableName)
}

func TestConnectorInboundRouteExecutionSpecColumns(t *testing.T) {
	const tableName = "_System_ConnectorInboundRoute"
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != tableName {
			continue
		}
		columns := map[string]string{}
		defaults := map[string]string{}
		for _, col := range def.Columns {
			columns[col.Name] = col.Type
			defaults[col.Name] = col.Default
		}
		want := map[string]string{
			"InvocationMode":        "VARCHAR(64)",
			"BatchInputName":        "VARCHAR(255)",
			"BatchResultOutputName": "VARCHAR(255)",
			"MaxBatchSize":          "INT",
			"QueueClass":            "VARCHAR(32)",
			"FlowExecutionFilter":   "JSON",
		}
		for name, typ := range want {
			if got := columns[name]; got != typ {
				t.Fatalf("%s.%s type = %q, want %q", tableName, name, got, typ)
			}
		}
		wantDefaults := map[string]string{
			"InvocationMode":        "'per_event'",
			"BatchInputName":        "'events'",
			"BatchResultOutputName": "'eventResults'",
			"MaxBatchSize":          "100",
			"QueueClass":            "'default'",
		}
		for name, wantDefault := range wantDefaults {
			if got := defaults[name]; got != wantDefault {
				t.Fatalf("%s.%s default = %q, want %q", tableName, name, got, wantDefault)
			}
		}
		return
	}
	t.Fatalf("%s not found in system_tables.json", tableName)
}

func TestConnectorEventQueueClassAndSkippedStatus(t *testing.T) {
	const tableName = "_System_ConnectorEvent"
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != tableName {
			continue
		}
		var queueClassType string
		for _, col := range def.Columns {
			if col.Name == "QueueClass" {
				queueClassType = col.Type
			}
		}
		if queueClassType != "VARCHAR(32)" {
			t.Fatalf("%s.QueueClass type = %q, want VARCHAR(32)", tableName, queueClassType)
		}
		var foundClaim, foundQueueClaim bool
		for _, idx := range def.Indices {
			switch idx.Name {
			case "idx_connector_event_claim":
				foundClaim = strings.Join(idx.Columns, ",") == "InstalledPackId,Status,CreatedDate,DeliverySequence"
			case "idx_connector_event_queue_claim":
				foundQueueClaim = strings.Join(idx.Columns, ",") == "InstalledPackId,QueueClass,Status,CreatedDate,DeliverySequence"
			}
		}
		if !foundClaim {
			t.Fatalf("%s.idx_connector_event_claim must keep claim hot-path prefix", tableName)
		}
		if !foundQueueClaim {
			t.Fatalf("%s.idx_connector_event_queue_claim with QueueClass not found", tableName)
		}
		return
	}
	t.Fatalf("%s not found in system_tables.json", tableName)
}

// TestConnectorInboundRouteRequiredReadPermissionIsViewSetup pins requiredReadPermission
// to "ViewSetup". This matches the contract §6 table schema (unlike rate_limit and
// webhook_secret which use BLOCKED for read — routing lookups are performed by
// DBRouteResolver, a platform-internal service, and ViewSetup-gated admin reads
// are appropriate for route inspection).
func TestConnectorInboundRouteRequiredReadPermissionIsViewSetup(t *testing.T) {
	const (
		tableName = "_System_ConnectorInboundRoute"
		wantPerm  = "ViewSetup"
	)
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != tableName {
			continue
		}
		if def.RequiredReadPermission != wantPerm {
			t.Fatalf(
				"%s.requiredReadPermission = %q, want %q; "+
					"route rows are admin-readable via ViewSetup per contract §6.",
				tableName, def.RequiredReadPermission, wantPerm,
			)
		}
		return
	}
	t.Fatalf("%s not found in system_tables.json", tableName)
}

// TestConnectorInboundRouteTableType pins tableType to "system_metadata".
// Precedent: _System_ConnectorRateLimit (system_tables.json) uses the same value
// for connector-substrate integration tables (NCRM-INT-11b fix for missing descriptor).
func TestConnectorInboundRouteTableType(t *testing.T) {
	const (
		tableName = "_System_ConnectorInboundRoute"
		wantType  = "system_metadata"
	)
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != tableName {
			continue
		}
		if def.TableType != wantType {
			t.Fatalf("%s.tableType = %q, want %q; "+
				"missing tableType causes bootstrap to fall back to empty string "+
				"when materializing _System_Object rows (NCRM-INT-11b blocker 2).",
				tableName, def.TableType, wantType)
		}
		return
	}
	t.Fatalf("%s not found in system_tables.json", tableName)
}

// TestConnectorInboundRouteCategory pins category to "integration".
// Precedent: _System_ConnectorRateLimit (system_tables.json) uses the same value.
func TestConnectorInboundRouteCategory(t *testing.T) {
	const (
		tableName    = "_System_ConnectorInboundRoute"
		wantCategory = "integration"
	)
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != tableName {
			continue
		}
		if def.Category != wantCategory {
			t.Fatalf("%s.category = %q, want %q; "+
				"missing category causes empty Category in _System_Table row (NCRM-INT-11b blocker 2).",
				tableName, def.Category, wantCategory)
		}
		return
	}
	t.Fatalf("%s not found in system_tables.json", tableName)
}

// TestConnectorInboundRouteHasDescription asserts a non-empty description is declared.
func TestConnectorInboundRouteHasDescription(t *testing.T) {
	const tableName = "_System_ConnectorInboundRoute"
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != tableName {
			continue
		}
		if def.Description == "" {
			t.Fatalf("%s.description is empty; "+
				"missing description causes empty Description in _System_Object (NCRM-INT-11b blocker 2).",
				tableName)
		}
		return
	}
	t.Fatalf("%s not found in system_tables.json", tableName)
}

// TestConnectorInboundRouteIdIsPrimaryKey asserts the Id column carries primaryKey: true.
// Without primaryKey:true the physical table is created without a primary key constraint
// (NCRM-INT-11b blocker 2; precedent: _System_ConnectorRateLimit Id column).
func TestConnectorInboundRouteIdIsPrimaryKey(t *testing.T) {
	const tableName = "_System_ConnectorInboundRoute"
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != tableName {
			continue
		}
		for _, col := range def.Columns {
			if col.Name == "Id" {
				if !col.PrimaryKey {
					t.Fatalf("%s.Id.primaryKey = false, want true; "+
						"without primaryKey:true the DDL creates the table without a PK constraint (NCRM-INT-11b blocker 2).",
						tableName)
				}
				return
			}
		}
		t.Fatalf("%s: Id column not found in column list", tableName)
	}
	t.Fatalf("%s not found in system_tables.json", tableName)
}

// TestConnectorInboundRouteUniqueIndex asserts that _System_ConnectorInboundRoute
// declares the unique index on (InstalledPackId, EventKind, Version).
//
// PR #1618 (NCRM-INT-11b) extended the unique key from (InstalledPackId, EventKind)
// to (InstalledPackId, EventKind, Version) to allow multiple versions of the same
// route to coexist in the table during in-flight event drain. The old 2-column
// unique index is explicitly dropped by dropObsoleteIndices in schema.go.
func TestConnectorInboundRouteUniqueIndex(t *testing.T) {
	const (
		tableName     = "_System_ConnectorInboundRoute"
		wantIndexName = "uniq_cir_pack_kind_version"
	)
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != tableName {
			continue
		}
		for _, idx := range def.Indices {
			if !idx.Unique {
				continue
			}
			if len(idx.Columns) == 3 &&
				idx.Columns[0] == "InstalledPackId" &&
				idx.Columns[1] == "EventKind" &&
				idx.Columns[2] == "Version" {
				if idx.Name != wantIndexName {
					t.Fatalf("%s unique index name = %q, want %q; "+
						"the explicit short name keeps fresh setup under TiDB/MySQL's 64-char identifier limit.",
						tableName, idx.Name, wantIndexName)
				}
				if len(idx.Name) > 64 {
					t.Fatalf("%s unique index name %q is %d chars, want <=64 for TiDB/MySQL.",
						tableName, idx.Name, len(idx.Name))
				}
				return // found
			}
		}
		t.Fatalf("%s: unique index on (InstalledPackId, EventKind, Version) not found; "+
			"this 3-column unique key is required since PR #1618 (NCRM-INT-11b) to allow "+
			"multiple route versions to coexist during in-flight event drain.",
			tableName)
	}
	t.Fatalf("%s not found in system_tables.json", tableName)
}
