package region

import (
	"context"
	"os"
	"strconv"
	"testing"

	"github.com/docker/docker/api/types/container"
	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/modules/mysql"
	pkgv2db "github.com/tidbcloud/pkgv2/db"
	"github.com/tidbcloud/pkgv2/gormutil"

	"github.com/tidbcloud/tidb-management-service/serverless-global-service/app/regional_proxy"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/app/register"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/adapter"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/service/cluster"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/service/region"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/service/sqluser"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/testdata"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/infra/adapterimpl"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/infra/adapterimpl/cli"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/infra/persistence/db"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/infra/persistence/migrate"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/infra/persistence/repo"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/pkg/config"
	"github.com/tidbcloud/tidb-management-service/utilities/clusterevent"
	"github.com/tidbcloud/tidb-management-service/utilities/clusterproxy"
	"github.com/tidbcloud/tidb-management-service/utilities/featuregate"
	"github.com/tidbcloud/tidb-management-service/utilities/idgenerator"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

var (
	provider         = testdata.GetAwsProvider()
	regionEn         = testdata.GetAwsRegionUsWest2()
	awsUsWest1Region = testdata.GetAwsRegionUsWest1()
)

func TestMain(m *testing.M) {
	var (
		database = "local_test"
		password = "password"
		user     = "root"
	)

	ctx := context.Background()

	// Refer to https://golang.testcontainers.org/features/garbage_collector/#ryuk.
	if err := os.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true"); err != nil {
		panic(err)
	}
	if err := os.Setenv("DOCKER_API_VERSION", "1.44"); err != nil {
		panic(err)
	}
	mysqlContainer, err := mysql.RunContainer(
		ctx,
		testcontainers.WithImage("gcr.io/pingcap-public/third-party/mysql:8.0"),
		mysql.WithDatabase(database),
		mysql.WithUsername(user),
		mysql.WithPassword(password),
		testcontainers.WithHostConfigModifier(func(hostConfig *container.HostConfig) {
			hostConfig.Tmpfs = map[string]string{"/var/lib/mysql": "rw"} // Use tmpfs to speed up tests.
		}),
	)
	if err != nil {
		panic(err)
	}
	defer func() {
		if err := mysqlContainer.Terminate(ctx); err != nil {
			panic(err)
		}
	}()

	host, _ := mysqlContainer.Host(ctx)
	portS, _ := mysqlContainer.MappedPort(ctx, "3306/tcp")
	port, _ := strconv.ParseUint(portS.Port(), 10, 16)

	config.Global().MysqlConfig = gormutil.Config{
		User:     user,
		Host:     host,
		Database: database,
		Password: password,
		Port:     uint16(port),
	}
	config.Global().RegionalServiceConfig.RegionalProxyServiceConfig.Enabled = true

	initFakeDependencies()
	if err := register.DiCall(migrate.RunDBMigrate); err != nil {
		panic(err)
	}

	// Ensure a clean initial dataset before tests run
	resetFixtures(ctx)

	if err := register.DiCall(func(workflowImpl workflow.Workflow) error {
		return workflowImpl.Start()
	}); err != nil {
		panic(err)
	}

	m.Run()
}

func initFakeDependencies() {
	register.Provide(db.NewGormDB)
	register.Provide(db.NewDBProvider)
	register.Provide(func() featuregate.Service {
		return featuregate.NewFeatureGate(config.Global().ServerConfig.FeatureGates, nil)
	})
	register.Provide(func(dbProvider pkgv2db.TransProvider) adapter.WALCommitter {
		return clusterevent.NewWALCommitter(dbProvider)
	})
	register.Provide(func(dbProvider pkgv2db.TransProvider, handler clusterevent.Publisher) clusterevent.WALWatcher {
		return clusterevent.NewWALWatcher(dbProvider, handler, "test")
	})
	register.Provide(clusterevent.NewPublisher)
	// Universal unique id
	register.Provide(func() idgenerator.IDGenerator {
		nid := idgenerator.NodeID(0)
		return idgenerator.NewSonyflakeID(nid, "")
	})
	register.Provide(func(provider pkgv2db.TransProvider, idGenerator idgenerator.IDGenerator) workflow.Workflow {
		w, err := workflow.NewWorkflow(
			workflow.ID("serverless-global-test-1"),
			register.GetMysqlDefaultConfig(),
			provider,
			idGenerator,
			workflow.WithWorkerMode(workflow.ModeCompetitive),
		)
		if err != nil {
			panic(err)
		}
		return w
	})

	register.Provide(repo.NewCloudProviderRepo)
	register.Provide(repo.NewClusterRepoImpl)
	register.Provide(repo.NewRegionRepoImpl)

	register.Provide(adapterimpl.NewFakeDataflowServiceImpl)
	register.Provide(adapterimpl.NewFakeServerlessFacade)
	register.Provide(adapterimpl.NewFakeEncryptor)
	register.Provide(adapterimpl.NewFakeBilling)
	register.Provide(adapterimpl.NewFakeSys9Lifecycle)

	register.Provide(regional_proxy.NewService)
	register.Provide(cli.NewFakeRegionalProxyClientImpl)
	register.Provide(adapterimpl.NewRegionalServiceProxy)
	register.Provide(adapterimpl.NewFakeRegionService)

	register.Provide(clusterproxy.NewFakeClusterProxyProvider)
	register.Provide(sqluser.NewSqlUserService)
	register.Provide(adapterimpl.NewFakeAccountImpl)

	register.Provide(cluster.NewClusterService)
	register.Provide(region.NewRegionServiceImpl)

	register.Provide(NewService)
}

// cleanDB truncates core tables to ensure a clean state.
func cleanDB(ctx context.Context) {
	_ = register.DiCall(func(provider pkgv2db.TransProvider) error {
		wdb := provider.WriteDB(ctx)
		if err := wdb.Exec("SET FOREIGN_KEY_CHECKS=0").Error; err != nil {
			return err
		}
		// Truncate child tables first, then parents
		tables := []string{
			"region_tenants",
			"regions",
			"providers",
		}
		for _, tb := range tables {
			if err := wdb.Exec("TRUNCATE TABLE " + tb).Error; err != nil {
				return err
			}
		}
		return wdb.Exec("SET FOREIGN_KEY_CHECKS=1").Error
	})
}

// resetFixtures clears DB and seeds initial test data.
func resetFixtures(ctx context.Context) {
	cleanDB(ctx)
	_ = register.DiCall(func(pr repository.CloudProviderRepo, rr repository.RegionRepo) error {
		if _, err := pr.Save(ctx, provider); err != nil {
			return err
		}
		if _, err := rr.Save(ctx, regionEn); err != nil {
			return err
		}
		if _, err := rr.Save(ctx, awsUsWest1Region); err != nil {
			return err
		}
		return nil
	})
}

// setupTest resets fixtures before each test for isolation.
func setupTest(t *testing.T) {
	t.Helper()
	resetFixtures(t.Context())
}
