package cluster

import (
	"context"
	"os"
	"strconv"
	"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"

	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/app/bff/orchestrator"
	internalstatus "github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/app/internalapi/customstatus"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/app/openapi/dedicated/cluster"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/app/openapi/dedicated/region"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/app/openapi/dedicated/tidbgroup"
	accountadapter "github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/account"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/customstatus"
	clustersvc "github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/dedicated/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/dataflowcli"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/eventbus/auditlogsender"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/globaldedicatedcli"
	"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 {
		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
	if err := auditlog.InitMockAuditLogSDK(context.TODO(), time.Second); err != nil {
		panic(err)
	}

	m.Run()
}

func initService(mockAccountCli accountadapter.Client) (openapidedicatedv1beta1.ClusterServiceServer, *orchestrator.APIOrchestrator, error) {
	gs, err := globaldedicatedcli.NewClient(&config.DedicatedGlobalService{
		Url:   "localhost:10002",
		Local: true,
	})
	if err != nil {
		return nil, nil, err
	}

	dataflowCli, err := dataflowcli.NewClient(&config.DataFlowService{
		Url:   "localhost:10003",
		Local: true,
	})
	if err != nil {
		return nil, nil, err
	}

	accountCli := mockAccountCli
	if accountCli == nil {
		accountCli, err = accountcli.NewClient(&config.AccountService{
			Url:   "localhost:10005",
			Local: true,
		})
		if err != nil {
			return nil, nil, err
		}
	}
	auditLogSender := auditlogsender.NewConsoleAuditLogSender()

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

	clusterRepo := repository.NewClusterRepository(dbProvider)
	resourceTagRepo := repository.NewResourceTagRepository(dbProvider)
	clusterTagBindingRepo := repository.NewClusterTagBindingRepository(dbProvider)
	customStatusRepo := repository.NewCustomStatusRepository(dbProvider)
	migrationClusterRepo := repository.NewMigrationClusterRepoRepo(dbProvider)
	maintenanceTaskExecutionRepo := repository.NewMaintenanceTaskExecutionRepo(dbProvider)
	encryptor := remoteencryptor.NewEncryptor("")

	if err := workflowsvc.RegisterDags(
		workflowimpl.GetWorkflow(),
		dbProvider,
		customStatusRepo,
		migrationClusterRepo,
		clusterRepo,
		nil,
		nil,
		nil,
		nil,
		nil,
		accountCli,
		encryptor,
		gs,
		dataflowCli,
		nil,
		nil,
	); err != nil {
		return nil, nil, err
	}

	clusterSvc, err := clustersvc.NewService(
		clusterRepo,
		resourceTagRepo,
		clusterTagBindingRepo,
		customStatusRepo,
		gs,
		dataflowCli,
		accountCli,
		encryptor,
		dbProvider,
		workflowimpl.GetWorkflow(),
		maintenanceTaskExecutionRepo,
	)
	if err != nil {
		return nil, nil, err
	}
	customStatusSvc := customstatus.NewService(clusterRepo, customStatusRepo, gs, nil, nil, dbProvider)
	openApiRegionApp := region.NewServer(gs, accountCli)

	openAPIServer := cluster.NewServer(clusterSvc, customStatusSvc, gs, auditLogSender, openApiRegionApp, accountCli, nil)
	tidbGroupServer := tidbgroup.NewServer(gs, clusterSvc, customStatusRepo, auditLogSender)

	return openAPIServer, orchestrator.NewAPIOrchestrator(
		workflowimpl.GetWorkflow(),
		internalstatus.NewServer(customStatusSvc),
		openAPIServer,
		RegisterActions(openAPIServer, tidbGroupServer),
	), nil
}
