package service

import (
	"context"
	"fmt"
	"sync"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	regionalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/regional/serverless/v1"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

type MockService struct {
	globalserverlessv1.UnimplementedSqlUserServiceServer
	globalserverlessv1.UnimplementedServerlessServiceServer

	name string
	// Map: TenantId - ProjectId - ClusterId - Cluster
	clusters        map[uint64]map[uint64]map[string]*globalserverlessv1.ServerlessServiceCluster
	next_cluster_id uint64
	lock            sync.Mutex
}

func NewMockService(name string) *MockService {
	return &MockService{
		name:            name,
		clusters:        make(map[uint64]map[uint64]map[string]*globalserverlessv1.ServerlessServiceCluster),
		next_cluster_id: 100000000,
	}
}

// For testing only
func (svc *MockService) Reset() {
	svc.lock.Lock()
	defer svc.lock.Unlock()
	svc.next_cluster_id = 100000000
}

// For testing only
func (svc *MockService) SetCreateClusterNextId(id uint64) {
	svc.lock.Lock()
	defer svc.lock.Unlock()
	svc.next_cluster_id = id
}

// For testing only
func (svc *MockService) CreateCluster(tenantId, projectId uint64) (clusterId string) {
	svc.lock.Lock()
	defer svc.lock.Unlock()
	projects, ok := svc.clusters[tenantId]
	if !ok {
		projects = make(map[uint64]map[string]*globalserverlessv1.ServerlessServiceCluster)
	}
	project, ok := projects[projectId]
	if !ok {
		project = make(map[string]*globalserverlessv1.ServerlessServiceCluster)
	}
	clusterIdVal := svc.next_cluster_id
	svc.next_cluster_id++
	clusterId = fmt.Sprintf("%v", clusterIdVal)
	cluster := &globalserverlessv1.ServerlessServiceCluster{
		ClusterId: clusterId,
		TenantId:  tenantId,
		ProjectId: projectId,
		Cluster: &regionalserverlessv1.ServerlessRegionalServiceClusterInfo{
			ClusterId: clusterId,
			TenantId:  tenantId,
			ProjectId: projectId,
		},
	}
	project[clusterId] = cluster
	projects[cluster.ProjectId] = project
	svc.clusters[cluster.TenantId] = projects
	return clusterId
}

func (svc *MockService) GetEncryptedCloudAdminPwd(
	ctx context.Context,
	in *globalserverlessv1.ServerlessServiceGetEncryptedCloudAdminPwdRequest,
) (*globalserverlessv1.ServerlessServiceGetEncryptedCloudAdminPwdResponse, error) {
	tilog.Info(ctx, "mock-serverless-global",
		zap.String("service", svc.name),
		zap.String("method", "GetEncryptedCloudAdminPwd"),
		zap.String("cluster-id", in.ClusterId))
	cluster := svc.getCluster(in.ClusterId)
	if cluster == nil {
		return nil, bizerr.New(bizerr.GrayscaleUpgradeDataNotInTargetSystem, "not found the cluster %s", in.ClusterId)
	}
	return &globalserverlessv1.ServerlessServiceGetEncryptedCloudAdminPwdResponse{}, nil
}

func (svc *MockService) ChangeSqlUserPasswd(
	ctx context.Context,
	in *globalserverlessv1.ChangeSqlUserPasswdRequest,
) (*globalserverlessv1.ChangeSqlUserPasswdResponse, error) {
	tilog.Info(ctx, "mock-serverless-global",
		zap.String("service", svc.name),
		zap.String("method", "ChangeSqlUserPasswd"),
		zap.String("cluster-id", in.ClusterId))
	cluster := svc.getCluster(in.ClusterId)
	if cluster == nil {
		return nil, bizerr.New(bizerr.GrayscaleUpgradeDataNotInTargetSystem, "not found the cluster %s", in.ClusterId)
	}
	return &globalserverlessv1.ChangeSqlUserPasswdResponse{}, nil
}

func (svc *MockService) getCluster(clusterId string) *globalserverlessv1.ServerlessServiceCluster {
	svc.lock.Lock()
	defer svc.lock.Unlock()
	for _, projects := range svc.clusters {
		for _, clusters := range projects {
			if cluster, ok := clusters[clusterId]; ok {
				return cluster
			}
		}
	}
	return nil
}
