package serviceconfiguration

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/go-sql-driver/mysql"
	"github.com/pingcap-inc/nexuscrm-connectors/features/connector/credentialgeneration"
)

func TestServiceConfigurationRepositorySchemaReadinessAgainstTiDB(t *testing.T) {
	db, cleanup := openServiceConfigurationRepositoryTestTiDB(t)
	defer cleanup()
	createServiceConfigurationRepositoryTestTables(t, db)
	repository := newServiceConfigurationRepositoryTestRepository(t, db)

	if err := repository.VerifySchema(t.Context()); err != nil {
		t.Fatalf("VerifySchema() error = %v", err)
	}
}

func TestServiceConfigurationRepositoryImmutableGraphAgainstTiDB(t *testing.T) {
	db, cleanup := openServiceConfigurationRepositoryTestTiDB(t)
	defer cleanup()
	createServiceConfigurationRepositoryTestTables(t, db)
	repository := newServiceConfigurationRepositoryTestRepository(t, db)
	credentials := serviceConfigurationTestCredentials(t)
	seedServiceConfigurationCredentialGenerations(t, db, credentials)

	input := serviceConfigurationTestInput("service-configuration-tidb")
	view := serviceConfigurationTestView()
	want, err := deriveGeneration(input, view, credentials)
	if err != nil {
		t.Fatalf("deriveGeneration(): %v", err)
	}

	stored, duplicate, err := repository.Publish(t.Context(), input, view)
	if err != nil || duplicate || !stored.equal(want) {
		t.Fatalf(
			"Publish() = equal:%t duplicate:%t err:%v",
			stored.equal(want),
			duplicate,
			err,
		)
	}
	loaded, found, err := repository.ResolveExact(t.Context(), input.ID)
	if err != nil || !found || !loaded.equal(want) {
		t.Fatalf(
			"ResolveExact() = equal:%t found:%t err:%v",
			loaded.equal(want),
			found,
			err,
		)
	}
	requireServiceConfigurationRepositoryGraphCounts(t, db, input.ID, 2, 2, 2)

	var isNull, byteLength int
	if err := db.QueryRowContext(t.Context(), `SELECT CanonicalConfiguration IS NULL,
       OCTET_LENGTH(CanonicalConfiguration)
  FROM _System_ConnectorServiceConfigurationBinding AS binding_configuration
  JOIN _System_ConnectorServiceConfigurationAccount AS account_configuration
    ON account_configuration.Id = binding_configuration.AccountConfigurationId
 WHERE account_configuration.ServiceConfigurationGenerationId = ?
   AND binding_configuration.CatalogBindingOrdinal = 0`, input.ID).Scan(&isNull, &byteLength); err != nil {
		t.Fatalf("read persisted empty canonical configuration: %v", err)
	}
	if isNull != 0 || byteLength != 0 {
		t.Fatalf("persisted canonical configuration isNull=%d bytes=%d, want 0/0", isNull, byteLength)
	}
	bindings := loaded.Accounts()[0].Bindings()
	if configuration := bindings[0].CanonicalConfiguration(); configuration == nil || len(configuration) != 0 {
		t.Fatalf("loaded CanonicalConfiguration() = %#v, want non-nil empty bytes", configuration)
	}

	retried, duplicate, err := repository.Publish(t.Context(), input, view)
	if err != nil || !duplicate || !retried.equal(want) {
		t.Fatalf(
			"Publish(exact retry) = equal:%t duplicate:%t err:%v",
			retried.equal(want),
			duplicate,
			err,
		)
	}
	requireServiceConfigurationRepositoryGraphCounts(t, db, input.ID, 2, 2, 2)

	conflict := serviceConfigurationTestInput(input.ID)
	conflict.OwnerRevision.ValueDigest = serviceConfigurationDigest("9")
	if _, _, err := repository.Publish(t.Context(), conflict, view); !errors.Is(err, ErrIdentityConflict) {
		t.Fatalf("Publish(conflict) error = %v, want ErrIdentityConflict", err)
	}
	loaded, found, err = repository.ResolveExact(t.Context(), input.ID)
	if err != nil || !found || !loaded.equal(want) {
		t.Fatalf(
			"ResolveExact() after conflict = equal:%t found:%t err:%v",
			loaded.equal(want),
			found,
			err,
		)
	}

	missingCredential := serviceConfigurationTestInput("service-configuration-missing-credential")
	missingCredential.Accounts[1].Bindings[0].CredentialSelections[0].CredentialGenerationID =
		"credential-generation-missing"
	if _, _, err := repository.Publish(t.Context(), missingCredential, view); !errors.Is(err, ErrCredentialGenerationUnavailable) {
		t.Fatalf("Publish(missing credential generation) error = %v, want ErrCredentialGenerationUnavailable", err)
	}
	var missingCount int
	if err := db.QueryRowContext(t.Context(), `SELECT COUNT(*)
FROM _System_ConnectorServiceConfigurationGeneration
WHERE Id = ?`, missingCredential.ID).Scan(&missingCount); err != nil {
		t.Fatalf("count rejected missing-credential generation: %v", err)
	}
	if missingCount != 0 {
		t.Fatalf("missing-credential Publish() stored %d parent rows, want 0", missingCount)
	}

	requireServiceConfigurationRepositoryDeleteRejected(
		t,
		db,
		`DELETE FROM _System_ConnectorServiceConfigurationGeneration WHERE Id = ?`,
		input.ID,
	)
	requireServiceConfigurationRepositoryDeleteRejected(
		t,
		db,
		`DELETE account_configuration
   FROM _System_ConnectorServiceConfigurationAccount AS account_configuration
  WHERE account_configuration.ServiceConfigurationGenerationId = ?
    AND account_configuration.ConnectorAccountId = ?`,
		input.ID,
		"account-a",
	)
	requireServiceConfigurationRepositoryDeleteRejected(
		t,
		db,
		`DELETE binding_configuration
   FROM _System_ConnectorServiceConfigurationBinding AS binding_configuration
   JOIN _System_ConnectorServiceConfigurationAccount AS account_configuration
     ON account_configuration.Id = binding_configuration.AccountConfigurationId
  WHERE account_configuration.ServiceConfigurationGenerationId = ?
    AND binding_configuration.CatalogBindingOrdinal = 1`,
		input.ID,
	)
	requireServiceConfigurationRepositoryDeleteRejected(
		t,
		db,
		`DELETE FROM _System_ConnectorCredentialGeneration WHERE Id = ?`,
		"credential-generation-a",
	)

	result, err := db.ExecContext(t.Context(), `UPDATE _System_ConnectorServiceConfigurationBinding AS binding_configuration
   JOIN _System_ConnectorServiceConfigurationAccount AS account_configuration
     ON account_configuration.Id = binding_configuration.AccountConfigurationId
   SET binding_configuration.CanonicalConfiguration = ?
 WHERE account_configuration.ServiceConfigurationGenerationId = ?
   AND binding_configuration.CatalogBindingOrdinal = 1`, []byte("corrupt"), input.ID)
	if err != nil {
		t.Fatalf("corrupt stored binding configuration: %v", err)
	}
	rows, err := result.RowsAffected()
	if err != nil {
		t.Fatalf("read stored corruption affected rows: %v", err)
	}
	if rows != 1 {
		t.Fatalf("stored corruption affected %d rows, want 1", rows)
	}
	if _, _, err := repository.ResolveExact(t.Context(), input.ID); !errors.Is(err, ErrStoredGenerationInvalid) {
		t.Fatalf("ResolveExact(corrupt graph) error = %v, want ErrStoredGenerationInvalid", err)
	}
}

func newServiceConfigurationRepositoryTestRepository(t *testing.T, db *sql.DB) *Repository {
	t.Helper()
	repository, err := NewRepository(db)
	if err != nil {
		t.Fatalf("NewRepository(): %v", err)
	}
	return repository
}

func seedServiceConfigurationCredentialGenerations(
	t *testing.T,
	db *sql.DB,
	generations []credentialgeneration.Generation,
) {
	t.Helper()
	for _, generation := range generations {
		if _, err := db.ExecContext(t.Context(), `INSERT INTO _System_ConnectorCredentialGeneration
       (Id, StableCredentialIdentity, ProviderScopeId, ConnectorAccountId,
        SecretBackendProtocol, SecretBackendVersionRef,
        TransportConfiguration, TransportConfigurationDigest,
        ReceiptProtocolVersion, OwnerReceipt, OwnerReceiptDigest)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
			generation.ID(),
			generation.StableCredentialIdentity(),
			generation.ProviderScopeID(),
			generation.ConnectorAccountID(),
			generation.SecretBackendProtocol(),
			generation.SecretBackendVersionRef(),
			generation.TransportConfiguration(),
			generation.TransportConfigurationDigest(),
			generation.ReceiptProtocolVersion(),
			generation.OwnerReceipt(),
			generation.OwnerReceiptDigest(),
		); err != nil {
			t.Fatalf("seed credential generation %q: %v", generation.ID(), err)
		}
	}
}

func requireServiceConfigurationRepositoryGraphCounts(
	t *testing.T,
	db *sql.DB,
	id string,
	wantAccounts, wantBindings, wantSelections int,
) {
	t.Helper()
	queries := []struct {
		name  string
		query string
		want  int
	}{
		{
			name: "accounts",
			query: `SELECT COUNT(*)
FROM _System_ConnectorServiceConfigurationAccount
WHERE ServiceConfigurationGenerationId = ?`,
			want: wantAccounts,
		},
		{
			name: "bindings",
			query: `SELECT COUNT(*)
FROM _System_ConnectorServiceConfigurationBinding AS binding_configuration
JOIN _System_ConnectorServiceConfigurationAccount AS account_configuration
  ON account_configuration.Id = binding_configuration.AccountConfigurationId
WHERE account_configuration.ServiceConfigurationGenerationId = ?`,
			want: wantBindings,
		},
		{
			name: "credential selections",
			query: `SELECT COUNT(*)
FROM _System_ConnectorServiceConfigurationCredentialSelection AS credential_selection
JOIN _System_ConnectorServiceConfigurationBinding AS binding_configuration
  ON binding_configuration.Id = credential_selection.BindingConfigurationId
JOIN _System_ConnectorServiceConfigurationAccount AS account_configuration
  ON account_configuration.Id = binding_configuration.AccountConfigurationId
WHERE account_configuration.ServiceConfigurationGenerationId = ?`,
			want: wantSelections,
		},
	}
	for _, query := range queries {
		var count int
		if err := db.QueryRowContext(t.Context(), query.query, id).Scan(&count); err != nil {
			t.Fatalf("count persisted %s: %v", query.name, err)
		}
		if count != query.want {
			t.Fatalf("persisted %s = %d, want %d", query.name, count, query.want)
		}
	}
}

func requireServiceConfigurationRepositoryDeleteRejected(
	t *testing.T,
	db *sql.DB,
	statement string,
	args ...any,
) {
	t.Helper()
	_, err := db.ExecContext(t.Context(), statement, args...)
	var mysqlError *mysql.MySQLError
	if !errors.As(err, &mysqlError) || mysqlError.Number != 1451 {
		t.Fatalf("foreign-key protected delete error = %v, want MySQL 1451", err)
	}
}

func createServiceConfigurationRepositoryTestTables(t *testing.T, db *sql.DB) {
	t.Helper()
	for _, statement := range []string{
		serviceConfigurationRepositoryCredentialGenerationTestDDL,
		serviceConfigurationRepositoryGenerationTestDDL,
		serviceConfigurationRepositoryAccountTestDDL,
		serviceConfigurationRepositoryBindingTestDDL,
		serviceConfigurationRepositoryCredentialSelectionTestDDL,
	} {
		if _, err := db.ExecContext(t.Context(), statement); err != nil {
			t.Fatalf("execute service-configuration repository fixture DDL: %v", err)
		}
	}
}

const serviceConfigurationRepositoryCredentialGenerationTestDDL = `CREATE TABLE _System_ConnectorCredentialGeneration (
  Id VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  StableCredentialIdentity VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  ProviderScopeId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  ConnectorAccountId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  SecretBackendProtocol VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  SecretBackendVersionRef VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  TransportConfiguration MEDIUMBLOB NOT NULL,
  TransportConfigurationDigest CHAR(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  ReceiptProtocolVersion VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  OwnerReceipt MEDIUMBLOB NOT NULL,
  OwnerReceiptDigest CHAR(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  CreatedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  LastModifiedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  PRIMARY KEY (Id),
  KEY idx_connector_credential_generation_identity (StableCredentialIdentity, Id),
  KEY idx_connector_credential_generation_scope (ProviderScopeId, ConnectorAccountId)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin`

const serviceConfigurationRepositoryGenerationTestDDL = `CREATE TABLE _System_ConnectorServiceConfigurationGeneration (
  Id VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  GenerationProtocolVersion VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  OrgId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  AppInstallationId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  AuthorizedWorkloadId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  CatalogRevision DECIMAL(20,0) NOT NULL,
  SchemaGeneration DECIMAL(20,0) NOT NULL,
  EngineGeneration DECIMAL(20,0) NOT NULL,
  SourceSnapshotDigest CHAR(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  CatalogViewDigest CHAR(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  CatalogReceiptProtocolVersion VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  CatalogReceiptCapabilityVersion VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  CatalogSecurityEpoch DECIMAL(20,0) NOT NULL,
  ConfigurationOwnerRevisionId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  ConfigurationOwnerRevisionDigest CHAR(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  GenerationDigest CHAR(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  ReceiptProtocolVersion VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  OwnerReceipt MEDIUMBLOB NOT NULL,
  OwnerReceiptDigest CHAR(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  AccountCount BIGINT NOT NULL,
  BindingCount BIGINT NOT NULL,
  CredentialSelectionCount BIGINT NOT NULL,
  CreatedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  LastModifiedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  PRIMARY KEY (Id),
  KEY idx_connector_service_configuration_catalog_view (CatalogViewDigest, Id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin`

const serviceConfigurationRepositoryAccountTestDDL = `CREATE TABLE _System_ConnectorServiceConfigurationAccount (
  Id VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  ServiceConfigurationGenerationId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  AccountOrdinal INT NOT NULL,
  ConnectorAccountId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  ProviderScopeId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  AccountRevisionId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  AccountRevisionDigest CHAR(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  ProviderRevisionId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  ProviderRevisionDigest CHAR(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  CreatedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  LastModifiedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  PRIMARY KEY (Id),
  UNIQUE KEY ux_connector_service_configuration_account_ordinal
    (ServiceConfigurationGenerationId, AccountOrdinal),
  UNIQUE KEY ux_connector_service_configuration_account_identity
    (ServiceConfigurationGenerationId, ConnectorAccountId),
  CONSTRAINT fk_connector_service_configuration_account_generation
    FOREIGN KEY (ServiceConfigurationGenerationId)
    REFERENCES _System_ConnectorServiceConfigurationGeneration (Id)
    ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin`

const serviceConfigurationRepositoryBindingTestDDL = `CREATE TABLE _System_ConnectorServiceConfigurationBinding (
  Id VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  AccountConfigurationId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  CatalogBindingOrdinal INT NOT NULL,
  ConfigurationRevisionId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  ConfigurationRevisionDigest CHAR(71) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  ConfigurationFormatVersion VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  CanonicalConfiguration MEDIUMBLOB NOT NULL,
  CreatedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  LastModifiedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  PRIMARY KEY (Id),
  UNIQUE KEY ux_connector_service_configuration_binding_ordinal
    (AccountConfigurationId, CatalogBindingOrdinal),
  CONSTRAINT fk_connector_service_configuration_binding_account
    FOREIGN KEY (AccountConfigurationId)
    REFERENCES _System_ConnectorServiceConfigurationAccount (Id)
    ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin`

const serviceConfigurationRepositoryCredentialSelectionTestDDL = `CREATE TABLE _System_ConnectorServiceConfigurationCredentialSelection (
  Id VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  BindingConfigurationId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  CredentialOrdinal INT NOT NULL,
  CredentialRef VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  CredentialGenerationId VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  CreatedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  LastModifiedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  PRIMARY KEY (Id),
  UNIQUE KEY ux_connector_service_configuration_credential_ordinal
    (BindingConfigurationId, CredentialOrdinal),
  UNIQUE KEY ux_connector_service_configuration_credential_ref
    (BindingConfigurationId, CredentialRef),
  UNIQUE KEY ux_connector_service_configuration_credential_generation
    (BindingConfigurationId, CredentialGenerationId),
  CONSTRAINT fk_connector_service_configuration_selection_binding
    FOREIGN KEY (BindingConfigurationId)
    REFERENCES _System_ConnectorServiceConfigurationBinding (Id)
    ON UPDATE RESTRICT ON DELETE RESTRICT,
  CONSTRAINT fk_connector_service_configuration_selection_credential
    FOREIGN KEY (CredentialGenerationId)
    REFERENCES _System_ConnectorCredentialGeneration (Id)
    ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin`

func openServiceConfigurationRepositoryTestTiDB(t *testing.T) (*sql.DB, func()) {
	t.Helper()
	host := serviceConfigurationRepositoryEnvironmentOr("TIDB_HOST", "127.0.0.1")
	port := serviceConfigurationRepositoryEnvironmentOr("TIDB_PORT", "4001")
	user := serviceConfigurationRepositoryEnvironmentOr("TIDB_USER", "root")
	password := os.Getenv("TIDB_PASSWORD")
	tlsParameter := ""
	switch strings.ToLower(strings.TrimSpace(os.Getenv("TIDB_TLS"))) {
	case "1", "true", "yes", "require", "verify-full":
		tlsParameter = "&tls=true"
	}

	admin, err := sql.Open("mysql", fmt.Sprintf(
		"%s:%s@tcp(%s:%s)/?parseTime=true&loc=UTC&timeout=2s%s",
		user,
		password,
		host,
		port,
		tlsParameter,
	))
	if err != nil {
		unavailableServiceConfigurationRepositoryTiDB(t, "open admin connection: %v", err)
	}
	if err := admin.PingContext(context.Background()); err != nil {
		_ = admin.Close()
		unavailableServiceConfigurationRepositoryTiDB(t, "TiDB not reachable at %s:%s: %v", host, port, err)
	}

	databaseName := fmt.Sprintf("nexuscrm_service_configuration_%d", time.Now().UnixNano())
	if _, err := admin.ExecContext(t.Context(), "CREATE DATABASE `"+databaseName+"`"); err != nil {
		_ = admin.Close()
		unavailableServiceConfigurationRepositoryTiDB(t, "create temporary database: %v", err)
	}
	db, err := sql.Open("mysql", fmt.Sprintf(
		"%s:%s@tcp(%s:%s)/%s?parseTime=true&loc=UTC&timeout=2s%s",
		user,
		password,
		host,
		port,
		databaseName,
		tlsParameter,
	))
	if err != nil {
		_, _ = admin.ExecContext(context.Background(), "DROP DATABASE IF EXISTS `"+databaseName+"`")
		_ = admin.Close()
		t.Fatalf("open service-configuration repository test database: %v", err)
	}
	db.SetMaxOpenConns(16)
	if err := db.PingContext(t.Context()); err != nil {
		_ = db.Close()
		_, _ = admin.ExecContext(context.Background(), "DROP DATABASE IF EXISTS `"+databaseName+"`")
		_ = admin.Close()
		t.Fatalf("ping service-configuration repository test database: %v", err)
	}

	cleanup := func() {
		_ = db.Close()
		_, _ = admin.ExecContext(context.Background(), "DROP DATABASE IF EXISTS `"+databaseName+"`")
		_ = admin.Close()
	}
	return db, cleanup
}

func unavailableServiceConfigurationRepositoryTiDB(t *testing.T, format string, args ...any) {
	t.Helper()
	if os.Getenv("NEXUSCRM_TIDB_REQUIRED") == "1" {
		t.Fatalf("required TiDB integration unavailable: "+format, args...)
	}
	t.Skipf("skipping TiDB integration: "+format, args...)
}

func serviceConfigurationRepositoryEnvironmentOr(name, fallback string) string {
	if value := strings.TrimSpace(os.Getenv(name)); value != "" {
		return value
	}
	return fallback
}
