package cluster

import (
	"context"
	"fmt"
	"os"
	"strconv"
	"sync"
	"testing"
	"time"

	"github.com/docker/docker/api/types/container"
	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/modules/mysql"
	"github.com/tidbcloud/tiinf/sdk/auditlog"

	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/account"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/billing"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/customstatus"
	clustersvc "github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/serverless/cluster"
	workflowsvc "github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/workflow"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/accountcli"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/billingcli"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/eventbus/auditlogsender"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/globalserverlesscli"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/remoteencryptor"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/repository"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/repository/db"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/repository/migration"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/workflowimpl"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/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 (
		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 {
		_, _ = fmt.Fprintln(os.Stderr, err)
		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)

	cfg := db.Config{
		User:         user,
		Host:         host,
		Database:     database,
		InitDatabase: database,
		Password:     password,
		Port:         uint16(port),
	}
	gormdb := db.InitDB(&cfg, migration.Records)

	// init workflow and dbprovider
	dbprovider := db.NewProvider(gormdb)
	workflow, err := workflowimpl.InitWorkflow(cfg.ToMySQLConfigWithDefaults(), dbprovider, &config.Workflow{Mode: "competitive"})
	if err != nil {
		panic(err)
	}
	defer workflow.Close()

	// init fake audit log
	err = auditlog.InitMockAuditLogSDK(context.TODO(), time.Second)
	if err != nil {
		panic(err)
	}

	m.Run()
}

var freeClusterQuotaConfig = config.BizConfig{
	ServerlessFreeQuotaMaxCount: 100,
}

var (
	registerDagsOnce sync.Once
	registerDagsErr  error
)

func initService() (Server, error) {
	return initServiceWithAccountCli(nil)
}

func initServiceWithAccountCli(accountCli account.Client) (Server, error) {
	return initServiceWithAccountAndBillingCli(accountCli, nil)
}

func initServiceWithAccountAndBillingCli(accountCli account.Client, billingCli billing.Client) (Server, error) {
	clusterSvc, auditLogSender, customStatusSvc, err := initDependenciesWithAccountAndBillingCli(accountCli, billingCli)
	if err != nil {
		return nil, err
	}

	return NewServer(clusterSvc, customStatusSvc, auditLogSender, accountCli, nil), nil
}

func initDependenciesWithAccountAndBillingCli(
	accountCli account.Client,
	billingCli billing.Client,
) (clustersvc.Service, eventbus.ConsoleAuditLogSender, customstatus.Service, error) {
	gs, err := globalserverlesscli.NewClient(&config.ServerlessGlobalService{
		Url:   "localhost:10001",
		Local: true,
	})
	if err != nil {
		return nil, nil, nil, err
	}

	// use default db provider
	dbProvider := db.NewProvider(nil)

	clusterRepo := repository.NewClusterRepository(dbProvider)
	resourceTagRepo := repository.NewResourceTagRepository(dbProvider)
	clusterTagBindingRepo := repository.NewClusterTagBindingRepository(dbProvider)
	zeroRentRecordRepo := repository.NewZeroRentRecordRepository(dbProvider)
	shadowPoolRepo := repository.NewShadowPoolRepository(dbProvider)
	customStatusRepo := repository.NewCustomStatusRepository(dbProvider)
	migrationClusterRepo := repository.NewMigrationClusterRepoRepo(dbProvider)
	lockRepo := repository.NewLockRepo(dbProvider)

	if accountCli == nil {
		accountCli, err = accountcli.NewClient(&config.AccountService{
			Url:   "localhost:10005",
			Local: true,
		})
		if err != nil {
			return nil, nil, nil, err
		}
	}
	if billingCli == nil {
		billingCli = billingcli.NewBillingClient(&config.Config{
			Billing: &config.BillingService{
				Billing2Url: "localhost:10006",
				Local:       true,
			},
		})
	}
	auditLogSender := auditlogsender.NewConsoleAuditLogSender()
	encryptor := remoteencryptor.NewEncryptor("")
	orgRuConfigRepo := repository.NewOrganizationRuConfigRepo(dbProvider)

	registerDagsOnce.Do(func() {
		registerDagsErr = workflowsvc.RegisterDags(
			workflowimpl.GetWorkflow(),
			dbProvider,
			customStatusRepo,
			migrationClusterRepo,
			clusterRepo,
			nil,
			nil,
			nil,
			gs,
			nil,
			accountCli,
			encryptor,
			nil,
			nil,
			nil,
			nil,
		)
	})
	if registerDagsErr != nil {
		return nil, nil, nil, registerDagsErr
	}

	clusterSvc, err := clustersvc.NewService(
		clusterRepo,
		resourceTagRepo,
		clusterTagBindingRepo,
		zeroRentRecordRepo,
		customStatusRepo,
		lockRepo,
		shadowPoolRepo,
		gs,
		accountCli,
		billingCli,
		&freeClusterQuotaConfig,
		dbProvider,
		encryptor,
		workflowimpl.GetWorkflow(),
		orgRuConfigRepo,
		nil,
	)
	if err != nil {
		return nil, nil, nil, err
	}

	customStatusSvc := customstatus.NewService(clusterRepo, customStatusRepo, nil, gs, nil, dbProvider)
	return clusterSvc, auditLogSender, customStatusSvc, nil
}

func fakeRegionDisplayName(regionName string) string {
	return fmt.Sprintf("Fake Region Display Name %s", regionName)
}
