package service

import (
	"cmp"
	"context"
	"fmt"
	"slices"
	"sync"

	"github.com/samber/lo"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/emptypb"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	regionalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/regional/serverless/v1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	mockcoresvc "github.com/tidbcloud/tidb-management-service/tempo-proxy/mock-core-svc/service"
)

type MockClusterService struct {
	tidbmgmtservicev1.UnimplementedClusterServiceServer

	CoreSvc *mockcoresvc.MockCoreSvc
	name    string
	// Map: TenantId - ProjectId - ClusterId - Cluster
	clusters        map[uint64]map[uint64]map[uint64]*tidbmgmtservicev1.Cluster
	next_cluster_id uint64
	lock            sync.Mutex
}

func NewMockClusterService(name string) *MockClusterService {
	return &MockClusterService{
		name:            name,
		CoreSvc:         mockcoresvc.NewMockCoreSvc(name),
		clusters:        make(map[uint64]map[uint64]map[uint64]*tidbmgmtservicev1.Cluster),
		next_cluster_id: 100000000,
	}
}

// For testing only
func (svc *MockClusterService) Reset() {
	svc.lock.Lock()
	defer svc.lock.Unlock()
	svc.clusters = make(map[uint64]map[uint64]map[uint64]*tidbmgmtservicev1.Cluster)
	svc.next_cluster_id = 100000000
}

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

// For testing only
func (svc *MockClusterService) CreateCluster(tenantId, projectId uint64, isDevTier bool) uint64 {
	svc.lock.Lock()
	defer svc.lock.Unlock()
	projects, ok := svc.clusters[tenantId]
	if !ok {
		projects = make(map[uint64]map[uint64]*tidbmgmtservicev1.Cluster)
	}
	project, ok := projects[projectId]
	if !ok {
		project = make(map[uint64]*tidbmgmtservicev1.Cluster)
	}
	deployType := tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED
	if isDevTier {
		deployType = tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS
	}
	cluster := &tidbmgmtservicev1.Cluster{
		OrganizationId: tenantId,
		ProjectId:      projectId,
		ClusterId:      svc.next_cluster_id,
		DeployType:     deployType,
		CreatedBy:      "0",
	}
	if isDevTier {
		cluster.ClusterInfo = &tidbmgmtservicev1.Cluster_ServerlessCluster{
			ServerlessCluster: &globalserverlessv1.ServerlessServiceCluster{
				TenantId:  tenantId,
				ProjectId: projectId,
				ClusterId: fmt.Sprintf("%v", cluster.ClusterId),
				Cluster: &regionalserverlessv1.ServerlessRegionalServiceClusterInfo{
					TenantId:  tenantId,
					ProjectId: projectId,
					ClusterId: fmt.Sprintf("%v", cluster.ClusterId),
					Region:    &regionalserverlessv1.Region{},
				},
			},
		}
	} else {
		cluster.ClusterInfo = &tidbmgmtservicev1.Cluster_DedicatedCluster{
			DedicatedCluster: &globaldedicatedv1.Cluster{
				OrganizationId: tenantId,
				ProjectId:      projectId,
				ClusterId:      cluster.ClusterId,
			},
		}
	}
	project[cluster.ClusterId] = cluster
	projects[cluster.ProjectId] = project
	svc.clusters[cluster.OrganizationId] = projects
	svc.next_cluster_id++
	return cluster.ClusterId
}

func (svc *MockClusterService) ListClusters(
	ctx context.Context,
	in *tidbmgmtservicev1.ListClustersRequest,
) (out *tidbmgmtservicev1.ListClustersResponse, err error) {
	tilog.PlainInfo("mock-mgmt-svc",
		zap.String("service", svc.name),
		zap.String("method", "ListClusters"))
	resp := &tidbmgmtservicev1.ListClustersResponse{
		Clusters: []*tidbmgmtservicev1.Cluster{},
		Total:    0,
	}
	svc.lock.Lock()
	defer svc.lock.Unlock()
	for tenant, projects := range svc.clusters {
		if len(in.Filter.TenantIds) != 0 && !lo.Contains(in.Filter.TenantIds, tenant) {
			continue
		}
		for project, clusters := range projects {
			if len(in.Filter.ProjectIds) != 0 && !lo.Contains(in.Filter.ProjectIds, project) {
				continue
			}
			for clusterId, cluster := range clusters {
				if len(in.Filter.ClusterIds) != 0 && !lo.Contains(in.Filter.ClusterIds, clusterId) {
					continue
				}
				if in.Filter.ClusterType != nil && cluster.DeployType != *in.Filter.ClusterType {
					continue
				}
				resp.Clusters = append(resp.Clusters, cluster)
				resp.Total++
			}
		}
	}

	if in.PageSize != 0 && in.Page != 0 {
		slices.SortFunc(resp.Clusters, func(a, b *tidbmgmtservicev1.Cluster) int {
			return cmp.Compare(a.ClusterId, b.ClusterId)
		})
		// Pager is base-1
		offset := (in.Page - 1) * in.PageSize
		if offset >= uint32(len(resp.Clusters)) {
			resp.Clusters = []*tidbmgmtservicev1.Cluster{}
		} else {
			resp.Clusters = resp.Clusters[offset:]
			if uint32(len(resp.Clusters)) > in.PageSize {
				resp.Clusters = resp.Clusters[0:in.PageSize]
			}
			resp.Total = uint32(len(resp.Clusters))
		}
	}

	return resp, nil
}

func (svc *MockClusterService) CleanClusters(
	ctx context.Context,
	in *tidbmgmtservicev1.CleanClustersRequest,
) (*emptypb.Empty, error) {
	tilog.PlainInfo("mock-mgmt-svc",
		zap.String("service", svc.name),
		zap.String("method", "CleanClusters"))
	svc.lock.Lock()
	defer svc.lock.Unlock()
	projects, ok := svc.clusters[in.OrganizationId]
	if !ok {
		return new(emptypb.Empty), nil
	}
	newProjectsClusters := make(map[uint64]map[uint64]*tidbmgmtservicev1.Cluster)
	for project, clusters := range projects {
		for clusterId, cluster := range clusters {
			if in.DeployType == cluster.DeployType {
				continue
			}
			newClusters, ok := newProjectsClusters[project]
			if !ok {
				newClusters = make(map[uint64]*tidbmgmtservicev1.Cluster)
			}
			newClusters[clusterId] = cluster
			newProjectsClusters[project] = newClusters
		}
	}
	svc.clusters[in.OrganizationId] = newProjectsClusters
	return new(emptypb.Empty), nil
}
