package service

import (
	"context"
	"dataservices/commons/config"
	"dataservices/commons/constants"
	"dataservices/commons/grpc"
	"dataservices/commons/logger"
	"dataservices/internal/dto/common"
	"dataservices/internal/entity"
	"dataservices/internal/repository"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"go.uber.org/zap"
)

var (
	dedicatedOrgWhitelist map[uint64]struct{}
	whitelistMutex        sync.RWMutex
)

func SetDedicatedOrgWhitelist(orgWhitelist map[uint64]struct{}) {
	whitelistMutex.Lock()
	defer whitelistMutex.Unlock()
	dedicatedOrgWhitelist = orgWhitelist
}

func GetDedicatedOrgWhitelistDiff(ctx context.Context, newOrgWhitelist map[uint64]struct{}) ([]uint64, error) {

	orgWhitelist, err := getDedicatedOrgWhitelistWithLock(ctx)
	if err != nil {
		return nil, err
	}

	var orgIDs []uint64
	for orgID := range newOrgWhitelist {
		if _, ok := orgWhitelist[orgID]; !ok {
			orgIDs = append(orgIDs, orgID)
		}
	}
	for orgID := range orgWhitelist {
		if _, ok := newOrgWhitelist[orgID]; !ok {
			orgIDs = append(orgIDs, orgID)
		}
	}
	return orgIDs, nil
}

func InDedicatedOrgWhitelist(ctx context.Context, orgID uint64) (bool, error) {
	orgWhitelist, err := getDedicatedOrgWhitelistWithLock(ctx)
	if err != nil {
		return false, err
	}
	_, ok := orgWhitelist[orgID]
	return ok, nil
}

func getDedicatedOrgWhitelistWithLock(ctx context.Context) (map[uint64]struct{}, error) {
	whitelistMutex.RLock()

	if dedicatedOrgWhitelist != nil {
		whitelistMutex.RUnlock()
		return dedicatedOrgWhitelist, nil
	}
	whitelistMutex.RUnlock()

	whitelistMutex.Lock()
	defer whitelistMutex.Unlock()

	if dedicatedOrgWhitelist != nil {
		return dedicatedOrgWhitelist, nil
	}

	var err error
	dedicatedOrgWhitelist, err = LoadDedicatedOrgWhitelist(ctx)
	return dedicatedOrgWhitelist, err
}

func LoadDedicatedOrgWhitelist(ctx context.Context) (orgMap map[uint64]struct{}, err error) {
	startTime := time.Now()
	defer func() {
		logger.LogWithError(ctx, err, "load dedicated org whitelist complete", zap.Any("org_whitelist", orgMap), zap.Duration("cost", time.Since(startTime)))
	}()
	orgMap = make(map[uint64]struct{})

	resp, err := grpc.GetWhitelist(ctx, constants.DataserviceEnableDedicatedExpName)
	if err != nil {
		return nil, err
	}
	for _, orgID := range resp.OrgIds {
		orgMap[orgID] = struct{}{}
	}

	resp, err = grpc.GetWhitelist(ctx, constants.Chat2QueryEnableDedicatedExpName)
	if err != nil {
		return nil, err
	}
	for _, orgID := range resp.OrgIds {
		orgMap[orgID] = struct{}{}
	}
	return orgMap, nil
}

func PutPrivatelinkTask(ctx context.Context, projectID, clusterID uint64, cloud, region string, isDeleteCluster bool, source string) error {
	// If the regional dataservice-control/dataapp-control isn't deployed for this
	// cluster's (cloud, region), privatelink tasks are a no-op and should be skipped.
	if !config.SupportsDataserviceControl(cloud, region) {
		logger.Info(ctx, "skip PutPrivatelinkTask for unsupported dataservice-control region",
			zap.Uint64("project_id", projectID),
			zap.Uint64("cluster_id", clusterID),
			zap.String("provider", cloud),
			zap.String("region", region),
			zap.Bool("is_delete_cluster", isDeleteCluster),
			zap.String("source", source),
		)
		return nil
	}
	logger.Info(ctx, "insert one cluster privatelink task", zap.Uint64("project_id", projectID), zap.Uint64("cluster_id", clusterID), zap.Bool("is_delete_cluster", isDeleteCluster), zap.String("source", source))

	// Add cluster level locks to prevent concurrency
	cacheManager := NewCacheManager()
	lockKey := fmt.Sprintf("putprivatelinktask:%d", clusterID)
	if err := cacheManager.Lock(ctx, lockKey, time.Minute); err != nil {
		return err
	}
	defer cacheManager.Unlock(ctx, lockKey)

	status := []string{entity.CommonTaskStatusPending, entity.CommonTaskStatusRunning, entity.CommonTaskStatusDormant}
	commonTasks, err := repository.SelectCommonTasksByEvent(ctx, entity.CommonTaskTypePrivatelink, clusterID, status)
	if err != nil {
		return err
	}

	// content := map[string]any{
	// 	"is_delete_cluster": isDeleteCluster,
	// 	"project_id":        projectID,
	// 	"source":            source,
	// }
	content := common.PrivatelinkTaskContent{
		IsDeleteCluster: isDeleteCluster,
		ProjectID:       projectID,
		Source:          source,
	}
	contentBytes, _ := json.Marshal(content)
	task := &entity.CommonTask{
		TaskType: entity.CommonTaskTypePrivatelink,
		EventID:  clusterID,
		Status:   entity.CommonTaskStatusPending,
		Content:  string(contentBytes),
	}
	for _, commonTask := range commonTasks {
		switch commonTask.Status {
		case entity.CommonTaskStatusPending:
			// already has a same task, skip this task, nothing to do
			return nil
		default:
			if task.ParentID == 0 {
				task.ParentID = commonTask.ID
			}
		}
	}
	return repository.CreateCommonTask(ctx, task)
}
