package spu

import (
	"context"
	"fmt"
	"os"
	"strconv"
	"sync/atomic"
	"testing"

	"github.com/docker/docker/api/types/container"
	"github.com/samber/lo"
	"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/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/db"
	migrate "github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/migration"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/pkg/config"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/pkg/tempomigrate/convert"
	featgatesvc "github.com/tidbcloud/tidb-management-service/utilities/featuregate"
)

var (
	dbProvider atomic.Pointer[pkgv2db.TransProvider]
	globalConf atomic.Pointer[config.Config]
)

// Tests in this package will test service functions that interact with the schema.
// Third-party service dependencies are mocked and not tested.
func TestMain(m *testing.M) {
	var (
		ctx = context.Background()

		c = &config.Config{
			MysqlConfig: gormutil.Config{
				User:     "root",
				Database: "local_test",
				Password: "password",
				LogLevel: "warn",
			},
			ServerConfig: config.ServerConfig{
				FeatureGates: map[string]featgatesvc.FeatureGate{
					// Force arm since the testdata is arm.
					featgatesvc.PreferArm: {All: true, DisabledCloudProviders: []string{"gcp", "azure"}},
				},
			},
		}
	)

	mysqlContainer, host, port, err := startMysqlContainer(
		ctx,
		c.MysqlConfig.Database,
		c.MysqlConfig.User,
		c.MysqlConfig.Password,
	)
	if err != nil {
		panic(err)
	}
	defer func() {
		if err := mysqlContainer.Terminate(ctx); err != nil {
			panic(err)
		}
	}()

	c.MysqlConfig.Host = host
	c.MysqlConfig.Port = port

	// Save back config to global.
	// Other tests in this package could use this config.
	globalConf.Store(c)

	// Create schemas.
	_, pvd, _ := db.NewDBProvider(db.NewDB(globalConf.Load()))
	if err := migrate.RunDBMigrate(pvd.WriteDB(ctx)); err != nil {
		panic(err)
	}

	// Save back dbProvider to test global.
	dbProvider.Store(&pvd)

	if err := initDBRegionRecord(ctx, pvd); err != nil {
		panic(err)
	}

	initDBTierRecord(ctx, pvd)

	m.Run()
}

func startMysqlContainer(
	ctx context.Context,
	dbName, user, passwd string,
) (c *mysql.MySQLContainer, host string, port uint16, err error) {
	// Refer to https://golang.testcontainers.org/features/garbage_collector/#ryuk.
	if err := os.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true"); err != nil {
		_, _ = fmt.Fprintln(os.Stderr, err)
		return nil, "", 0, err
	}
	if err := os.Setenv("DOCKER_API_VERSION", "1.44"); err != nil {
		_, _ = fmt.Fprintln(os.Stderr, err)
		return nil, "", 0, err
	}
	mysqlContainer, err := mysql.RunContainer(
		ctx,
		testcontainers.WithImage("gcr.io/pingcap-public/third-party/mysql:8.0"),
		mysql.WithDatabase(dbName),
		mysql.WithUsername(user),
		mysql.WithPassword(passwd),
		testcontainers.WithHostConfigModifier(func(hostConfig *container.HostConfig) {
			hostConfig.Tmpfs = map[string]string{"/var/lib/mysql": "rw"} // Use tmpfs to speed up tests.
		}),
	)
	if err != nil {
		return nil, "", 0, err
	}

	host, err = mysqlContainer.Host(ctx)
	if err != nil {
		return nil, "", 0, err
	}
	portS, err := mysqlContainer.MappedPort(ctx, "3306/tcp")
	if err != nil {
		return nil, "", 0, err
	}
	portParsed, err := strconv.ParseUint(portS.Port(), 10, 16)
	if err != nil {
		return nil, "", 0, err
	}

	return mysqlContainer, host, uint16(portParsed), nil
}

func initDBRegionRecord(ctx context.Context, d pkgv2db.Provider) error {
	providers := convert.GetDedicateGlobalProviders()
	for i := range providers {
		if err := d.WriteDB(ctx).Create(providers[i]).Error; err != nil {
			return err
		}
	}

	regions := convert.GetDedicateGlobalRegions()
	for i := range regions {
		if err := d.WriteDB(ctx).Create(regions[i]).Error; err != nil {
			return err
		}
	}

	return nil
}

func initDBTierRecord(ctx context.Context, d pkgv2db.Provider) {
	wdb := d.WriteDB(ctx)

	lo.ForEach(convert.GetDedicateGlobalInstanceTypes(), func(item schema.InstanceType, index int) {
		if err := wdb.Create(&item).Error; err != nil {
			tilog.Error(ctx, "create item failed", zap.Error(err))
			panic(err)
		}
	})

	lo.ForEach(convert.GetDedicateGlobalStorageTemplates(), func(item schema.StorageTemplate, index int) {
		if err := wdb.Create(&item).Error; err != nil {
			tilog.Error(ctx, "create item failed", zap.Error(err))
			panic(err)
		}
	})

	lo.ForEach(convert.GetDedicateGlobalTiers(), func(item schema.ComponentTier, index int) {
		if err := wdb.Create(&item).Error; err != nil {
			tilog.Error(ctx, "create item failed", zap.Error(err))
			panic(err)
		}
	})
	lo.ForEach(convert.GetDedicateGlobalTier2TierUnits(), func(item schema.TierTierUnit, index int) {
		if err := wdb.Create(&item).Error; err != nil {
			tilog.Error(ctx, "create item failed", zap.Error(err))
			panic(err)
		}
	})

	tierUnits, conditions := convert.GetDedicateGlobalTierUnits()
	lo.ForEach(tierUnits, func(item schema.ComponentTierUnit, index int) {
		if err := wdb.Create(&item).Error; err != nil {
			tilog.Error(ctx, "create item failed", zap.Error(err))
			panic(err)
		}
	})

	lo.ForEach(conditions, func(item schema.TierUnitCondition, index int) {
		if err := wdb.Create(&item).Error; err != nil {
			tilog.Error(ctx, "create item failed", zap.Error(err))
			panic(err)
		}
	})
}
