package service

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

	"github.com/samber/lo"
	coreapi "github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

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

func (svc *MockCoreSvc) CreateCluster(
	ctx context.Context,
	in *coreapi.CreateClusterReq,
) (*coreapi.CreateClusterResp, error) {
	tilog.PlainInfo("mock-core-svc",
		zap.String("service", svc.name),
		zap.String("method", "CreateCluster"))
	svc.lock.Lock()
	defer svc.lock.Unlock()
	projects, ok := svc.clusters[in.TenantId]
	if !ok {
		projects = make(map[uint64]map[uint64]*coreapi.Cluster)
	}
	project, ok := projects[in.ProjectId]
	if !ok {
		project = make(map[uint64]*coreapi.Cluster)
	}
	cluster := &coreapi.Cluster{
		TenantId:  in.TenantId,
		ProjectId: in.ProjectId,
		Id:        svc.next_cluster_id,
		IsDevTier: in.IsDevTier,
	}
	project[cluster.Id] = cluster
	projects[cluster.ProjectId] = project
	svc.clusters[cluster.TenantId] = projects
	svc.next_cluster_id++
	return &coreapi.CreateClusterResp{
		Id: cluster.Id,
	}, nil
}

func (svc *MockCoreSvc) CountClusters(
	ctx context.Context,
	in *coreapi.CountClustersReq,
) (*coreapi.CountClustersResp, error) {
	tilog.PlainInfo("mock-core-svc",
		zap.String("service", svc.name),
		zap.String("method", "CreateCluster"))
	svc.lock.Lock()
	defer svc.lock.Unlock()
	var count int64
	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.IsDevTier != nil && cluster.IsDevTier != *in.Filter.IsDevTier {
					continue
				}
				count++
			}
		}
	}
	return &coreapi.CountClustersResp{Count: count}, nil
}

func (svc *MockCoreSvc) DeleteCluster(
	ctx context.Context,
	in *coreapi.DeleteClusterReq,
) (*coreapi.DeleteClusterResp, error) {
	tilog.PlainInfo("mock-core-svc",
		zap.String("service", svc.name),
		zap.String("method", "CreateCluster"))
	svc.lock.Lock()
	defer svc.lock.Unlock()
	for _, projects := range svc.clusters {
		for _, clusters := range projects {
			if _, ok := clusters[in.ClusterId]; ok {
				delete(clusters, in.ClusterId)
				return &coreapi.DeleteClusterResp{}, nil
			}
		}
	}

	return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "cluster not found")
}

func (svc *MockCoreSvc) ListClusters(
	ctx context.Context,
	in *coreapi.ListClustersReq,
) (out *coreapi.ListClustersResp, err error) {
	tilog.PlainInfo("mock-core-svc",
		zap.String("service", svc.name),
		zap.String("method", "ListClusters"))
	resp := &coreapi.ListClustersResp{
		Clusters: make(map[uint64]*coreapi.Cluster),
	}
	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.IsDevTier != nil && cluster.IsDevTier != *in.Filter.IsDevTier {
					continue
				}
				resp.Clusters[clusterId] = cluster
				resp.OrderedClusterIds = append(resp.OrderedClusterIds, clusterId)
			}
		}
	}

	if in.Pager != nil && len(in.Sorters) == 0 {
		in.Sorters = []*base.Sorter{
			{
				OrderBy: "id",
				Sort:    base.Sort_SORT_ASC,
			},
		}
	}

	if in.Sorters != nil {
		if len(in.Sorters) != 1 || in.Sorters[0].OrderBy != "id" {
			return nil, fmt.Errorf("mock core-svc only support `id` as sorter")
		}
		slices.SortFunc(resp.OrderedClusterIds, func(a, b uint64) int {
			if in.Sorters[0].Sort == base.Sort_SORT_ASC {
				return cmp.Compare(a, b)
			}
			return cmp.Compare(b, a)
		})
	}

	if in.Pager != nil {
		// Pager is base-1
		allClusters := resp.Clusters
		resp.Clusters = map[uint64]*coreapi.Cluster{}
		offset := (in.Pager.Page - 1) * in.Pager.PageSize
		if offset >= int64(len(resp.OrderedClusterIds)) {
			resp.OrderedClusterIds = []uint64{}
		} else {
			resp.OrderedClusterIds = resp.OrderedClusterIds[offset:]
			if int64(len(resp.OrderedClusterIds)) > in.Pager.PageSize {
				resp.OrderedClusterIds = resp.OrderedClusterIds[0:in.Pager.PageSize]
			}
			for _, clusterId := range resp.OrderedClusterIds {
				resp.Clusters[clusterId] = allClusters[clusterId]
			}
		}
	}

	return resp, nil
}

// For testing only
func (svc *MockCoreSvc) SetAccessPointRespId(id uint64) {
	svc.accessPointRespId = id
}

func (svc *MockCoreSvc) QueryAccessPoints(
	ctx context.Context,
	in *coreapi.QueryAccessPointsReq,
) (*coreapi.QueryAccessPointsResp, error) {
	tilog.PlainInfo("mock-core-svc",
		zap.String("service", svc.name),
		zap.String("method", "QueryAccessPoints"))
	if in.Filter == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty filter")
	}
	if len(in.Filter.ClusterIds) != 1 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "only support 1 cluster-id")
	}
	if len(in.Filter.Ids) != 1 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "only support 1 id")
	}
	svc.lock.Lock()
	defer svc.lock.Unlock()
	var clusterIds []uint64
	for _, projects := range svc.clusters {
		for project, clusters := range projects {
			for clusterId := range clusters {
				clusterIds = append(clusterIds, clusterId)
				if !lo.Contains(in.Filter.ClusterIds, clusterId) {
					continue
				}
				resp := &coreapi.QueryAccessPointsResp{}
				resp.AccessPoints = append(resp.AccessPoints, &coreapi.AccessPoint{
					Id:        svc.accessPointRespId,
					ClusterId: clusterId,
					ProjectId: project,
				})
				return resp, nil
			}
		}
	}

	return nil, bizerr.New(bizerr.ErrCodeRecordNotFound,
		"cluster(s) not found, req: %v, exists: %v", in.Filter.ClusterIds, clusterIds)
}

func (svc *MockCoreSvc) GetClusterConfidentialInfo(
	ctx context.Context,
	in *coreapi.GetClusterConfidentialInfoReq,
) (*coreapi.GetClusterConfidentialInfoResp, error) {
	tilog.PlainInfo("mock-core-svc",
		zap.String("service", svc.name),
		zap.String("method", "GetClusterConfidentialInfo"))
	return &coreapi.GetClusterConfidentialInfoResp{}, nil
}

func (svc *MockCoreSvc) CleanClusters(
	ctx context.Context,
	in *coreapi.CleanClustersReq,
) (*coreapi.CleanClustersResp, error) {
	tilog.PlainInfo("mock-core-svc",
		zap.String("service", svc.name),
		zap.String("method", "CleanClusters"))
	svc.lock.Lock()
	defer svc.lock.Unlock()
	projects, ok := svc.clusters[in.TenantId]
	if !ok {
		return &coreapi.CleanClustersResp{}, nil
	}
	newProjectsClusters := make(map[uint64]map[uint64]*coreapi.Cluster)
	for project, clusters := range projects {
		for clusterId, cluster := range clusters {
			if in.IsDevTier == cluster.IsDevTier {
				continue
			}
			newClusters, ok := newProjectsClusters[project]
			if !ok {
				newClusters = make(map[uint64]*coreapi.Cluster)
			}
			newClusters[clusterId] = cluster
			newProjectsClusters[project] = newClusters
		}
	}
	svc.clusters[in.TenantId] = newProjectsClusters
	return &coreapi.CleanClustersResp{}, nil
}
