package bootstrapimpl

import (
	"regexp"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
)

// TestConnectorCalloutProfileTableExists asserts that _System_ConnectorCalloutProfile
// is declared in system_tables.json and carries the expected key columns.
func TestConnectorCalloutProfileTableExists(t *testing.T) {
	const tableName = "_System_ConnectorCalloutProfile"
	defs := GetManagedTableDefinitions()

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

		wantCols := []string{
			"Id", "InstalledPackId", "OwnerPack", "ProfileName", "CredentialRef", "EndpointClass",
			"RetryPolicy", "PageSpec", "BatchSpec", "ObjectPathMap",
			"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)
	}
}

// TestConnectorCalloutProfileRequiredReadPermissionIsBlocked pins requiredReadPermission
// to "BLOCKED". Generic SObject CRUD/query paths must not read callout profile rows
// directly; the only valid read path is features/connector/callout.LoadProfile.
func TestConnectorCalloutProfileRequiredReadPermissionIsBlocked(t *testing.T) {
	const (
		tableName = "_System_ConnectorCalloutProfile"
		wantPerm  = "BLOCKED"
	)
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != tableName {
			continue
		}
		if def.RequiredReadPermission != wantPerm {
			t.Fatalf(
				"%s.requiredReadPermission = %q, want %q; "+
					"callout profile rows must not be readable via generic CRUD. "+
					"Read path is connector_callout.LoadProfile only.",
				tableName, def.RequiredReadPermission, wantPerm,
			)
		}
		return
	}
	t.Fatalf("%s not found in system_tables.json", tableName)
}

// TestConnectorCalloutProfileRequiredWritePermissionIsBlocked pins requiredWritePermission
// to "BLOCKED". Direct writes would bypass connector pack validation and could inject
// arbitrary callout profiles outside the pack-install pipeline.
func TestConnectorCalloutProfileRequiredWritePermissionIsBlocked(t *testing.T) {
	const (
		tableName = "_System_ConnectorCalloutProfile"
		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-10). "+
					"Write path is ConnectorCalloutProfileComponent.Apply only.",
				tableName, def.RequiredWritePermission, wantPerm,
			)
		}
		return
	}
	t.Fatalf("%s not found in system_tables.json", tableName)
}

// TestConnectorCalloutProfileUniqueIndex asserts that _System_ConnectorCalloutProfile
// declares the unique index on (InstalledPackId, ProfileName).
func TestConnectorCalloutProfileUniqueIndex(t *testing.T) {
	const tableName = "_System_ConnectorCalloutProfile"
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != tableName {
			continue
		}
		for _, idx := range def.Indices {
			if !idx.Unique {
				continue
			}
			if len(idx.Columns) == 2 &&
				idx.Columns[0] == "InstalledPackId" &&
				idx.Columns[1] == "ProfileName" {
				return // found
			}
		}
		t.Fatalf("%s: unique index on (InstalledPackId, ProfileName) not found; declared indices: %v",
			tableName, func() []string {
				var out []string
				for _, idx := range def.Indices {
					prefix := "idx"
					if idx.Unique {
						prefix = "unique"
					}
					out = append(out, prefix+idx.Name)
				}
				return out
			}())
	}
	t.Fatalf("%s not found in system_tables.json", tableName)
}

func TestConnectorCalloutProfileInstalledPackIdColumnIsNullableForUpgrade(t *testing.T) {
	const tableName = "_System_ConnectorCalloutProfile"
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != tableName {
			continue
		}
		for _, col := range def.Columns {
			if col.Name == "InstalledPackId" {
				if !col.Nullable {
					t.Fatalf("%s.InstalledPackId Nullable = false, want true so existing profile rows can survive schema upgrade; runtime readers still fail closed on blank install IDs", tableName)
				}
				return
			}
		}
		t.Fatalf("%s.InstalledPackId column missing", tableName)
	}
	t.Fatalf("%s not found in system_tables.json", tableName)
}

func TestConnectorCalloutProfileOldUniqueIndexIsDroppedForUpgrade(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectExec(regexp.QuoteMeta("ALTER TABLE `_System_ConnectorCalloutProfile` DROP INDEX `ux_connector_callout_profile`")).
		WillReturnResult(sqlmock.NewResult(0, 0))

	if err := dropObsoleteIndices(db, map[string]bool{
		strings.ToLower("_System_ConnectorCalloutProfile"): true,
	}); err != nil {
		t.Fatalf("dropObsoleteIndices: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unmet sqlmock expectations: %v", err)
	}
}
