package cluster

import (
	"context"
	"fmt"
	"os"
	"strconv"
	"sync/atomic"
	"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/dedicated-global-service/domain/adapter/account"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/account/mock_account"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/billing/mock_billing"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/clusterconfig/mock_clusterconfig"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/clustermetric/mock_clustermetric"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/regionserver/mock_regionserver"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/remoteencryptor/mock_remoteencryptor"
	skusvc "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/service/spu"
	workflowsvc "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/service/workflow"
	auditlogrepoimpl "github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/auditlog"
	cidrrepoimpl "github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/cidr"
	clusterrepoimpl "github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/cluster"
	cmekrepoimpl "github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/cmek"
	configbackuprepoimpl "github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/configbackup"
	"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"
	endpointrepoimpl "github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/network"
	tenantquotaimpl "github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/quota"
	regionrepoimpl "github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/region"
	spurepoimpl "github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/spu"
	tidbgrouprepoimpl "github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/tidbgroup"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/pkg/config"
	"github.com/tidbcloud/tidb-management-service/utilities/clusterevent"
	clusterproxyutil "github.com/tidbcloud/tidb-management-service/utilities/clusterproxy"
	featgatesvc "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 (
	dbProvider  atomic.Pointer[pkgv2db.TransProvider]
	dbReadDB    atomic.Pointer[pkgv2db.Provider]
	dbTxner     atomic.Pointer[pkgv2db.Transactioner]
	globalConf  atomic.Pointer[config.Config]
	workflowMgr atomic.Pointer[workflow.Workflow]
)

// 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{},
			BizConfig: config.BizConfig{
				ClusterQuota: config.ClusterQuota{
					TiKV:    90,
					TiDB:    300,
					TiFlash: 10,
				},
				InsufficientResourceTimeout: "20m",
			},
		}
	)

	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

	globalConf.Store(c)

	// Create schemas.
	prov, pvd, txner := db.NewDBProvider(db.NewDB(c))
	if err := migrate.RunDBMigrate(pvd.WriteDB(ctx)); err != nil {
		panic(err)
	}
	dbProvider.Store(&pvd)
	dbReadDB.Store(&prov)
	dbTxner.Store(&txner)

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

	wf, err := workflow.NewWorkflowOnlyDb(
		config.GetMysqlDefaultConfig(globalConf.Load()),
		*dbReadDB.Load(),
		1,
		idgenerator.NewSonyflakeID(1, ""),
	)
	if err != nil {
		panic(err)
	}
	workflowMgr.Store(&wf)

	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 newTestService(
	encryptor *mock_remoteencryptor.MockClient,
	accountCli *mock_account.MockClient,
	regionServerCli *mock_regionserver.MockClient,
	metricCli *mock_clustermetric.MockClient,
	billingCli *mock_billing.MockClient,
	clusterConfigCli *mock_clusterconfig.MockClient,
	preferArm bool,
) *serviceImpl {
	dbPvd := *dbProvider.Load()
	cmekRepo := cmekrepoimpl.NewRepo(dbPvd)
	clusterRepo := clusterrepoimpl.NewClusterRepo(dbPvd)
	regionRepo := regionrepoimpl.NewRegionRepo(dbPvd)
	cidrRepo := cidrrepoimpl.NewCIDRRepo(dbPvd)
	instanceTypeRepo := spurepoimpl.NewInstanceMuRepo(dbPvd)
	pausePlanRepo := clusterrepoimpl.NewPausePlanRepo(dbPvd)
	cfg := globalConf.Load()
	cfg.ServerConfig.FeatureGates = map[string]featgatesvc.FeatureGate{
		featgatesvc.PreferArm: {All: preferArm, DisabledCloudProviders: []string{"gcp", "azure"}},
	}
	featGateSvc := featgatesvc.NewFeatureGate(
		cfg.ServerConfig.FeatureGates,
		account.BuildOrgCreateTimeFetcher(accountCli),
	)

	if err := workflowsvc.RegisterDags(
		*workflowMgr.Load(),
		nil, // componentRepo
		nil, // clusterRepo
		nil, // cidrRepo
		nil, // regionRepo
		nil, // tidbGroupRepo
		nil, // trafficFilterRepo
		nil, // lockRepo
		nil, // cmekRepo
		nil, // vpcPeeringRepo
		nil, // privateConnRepo
		nil, // pausePlanRepo
		nil, // auditLogFilterRuleRepo
		nil, // auditLogConfigRepo
		nil, // privatelinkservicemigrationRepo
		nil, // auditLogSvc
		nil, // regionalCli
		nil, // clusterConfigCli
		nil, // accountCli
		nil, // clusterProxyProvider
		nil, // encryptor
		nil, // committer
		nil, // transaction
		cfg,
	); err != nil {
		panic(err)
	}

	var s, err = NewService(
		globalConf.Load(),
		*workflowMgr.Load(),
		clusterRepo,
		clusterrepoimpl.NewComponentRepo(dbPvd),
		*dbTxner.Load(),
		skusvc.NewSkuService(
			spurepoimpl.NewSpuRepo(dbPvd),
			spurepoimpl.NewSkuRepo(dbPvd),
			spurepoimpl.NewInstanceMuRepo(dbPvd),
			spurepoimpl.NewStorageMuRepo(dbPvd),
		),
		regionServerCli,
		cmekRepo,
		featGateSvc,
		regionRepo,
		clusterrepoimpl.NewOperationRepo(dbPvd),
		tidbgrouprepoimpl.NewTidbGroupRepo(dbPvd),
		tenantquotaimpl.NewTenantQuotaRepo(dbPvd, globalConf.Load()),
		endpointrepoimpl.NewVpcPeeringRepo(dbPvd),
		pausePlanRepo,
		auditlogrepoimpl.NewConfigRepo(dbPvd),
		auditlogrepoimpl.NewFilterRuleRepo(dbPvd),
		metricCli,
		accountCli,
		billingCli,
		clusterproxyutil.NewFakeClusterProxyProvider(),
		clusterConfigCli,
		idgenerator.NewSonyflakeID(1, ""),
		endpointrepoimpl.NewTrafficFilterRepo(dbPvd),
		endpointrepoimpl.NewPrivateLinkEndpointRepo(dbPvd),
		clusterrepoimpl.NewClusterSnapshotRepo(dbPvd),
		encryptor,
		cidrRepo,
		clusterevent.NewWALCommitter(dbPvd),
		instanceTypeRepo,
		nil, // s3GetClient (not needed in tests)
		nil, // s3PresignClient (not needed in tests)
		configbackuprepoimpl.NewPolicyRepo(*dbReadDB.Load()),
		configbackuprepoimpl.NewBackupRepo(*dbReadDB.Load()),
	)
	if err != nil {
		panic(err)
	}

	if err := (*workflowMgr.Load()).Start(); err != nil {
		panic(err)
	}

	svc, ok := s.(*serviceImpl)
	if !ok {
		panic("unexpected cluster service implementation type")
	}
	return svc
}
