package server

import (
	"context"
	"fmt"

	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type sharedPoolMutationRule uint8

const (
	sharedPoolMutationUnspecified sharedPoolMutationRule = iota
	sharedPoolMutationBlockedWhilePaused
	sharedPoolMutationCreateTiDBCluster
	sharedPoolMutationUpdateVCPUsLimit
	sharedPoolMutationUpdateTags
	sharedPoolMutationDeleteInstance
	sharedPoolMutationDeletePool
)

// guardSharedPoolMutation is an admission guard, not execution fencing. A
// request that observed the pool before pause was accepted can still race it.
func guardSharedPoolMutation(sharedPool *entity.SharedPool, rule sharedPoolMutationRule) error {
	if sharedPool == nil {
		return status.Error(codes.Internal, "shared pool is nil")
	}
	switch rule {
	case sharedPoolMutationBlockedWhilePaused,
		sharedPoolMutationCreateTiDBCluster,
		sharedPoolMutationUpdateVCPUsLimit,
		sharedPoolMutationUpdateTags,
		sharedPoolMutationDeleteInstance,
		sharedPoolMutationDeletePool:
	default:
		return status.Errorf(codes.Internal, "unknown shared pool mutation rule %d", rule)
	}
	if err := entity.ValidatePauseResumeCombination(sharedPool); err != nil {
		return resourcePoolStatusError(codes.FailedPrecondition, reasonPauseResumeInvalidState, err.Error())
	}

	switch sharedPool.PauseResumeState {
	case entity.PauseResumeStateNone:
		return nil
	case entity.PauseResumeStatePausing, entity.PauseResumeStateResuming:
		if rule == sharedPoolMutationDeletePool {
			return nil
		}
		if sharedPool.PauseResumeState == entity.PauseResumeStateResuming &&
			rule == sharedPoolMutationCreateTiDBCluster {
			return nil
		}
		return resourcePoolStatusError(codes.FailedPrecondition, reasonPauseResumeInvalidState,
			"shared pool does not accept mutations while pause/resume is in progress")
	case entity.PauseResumeStatePaused:
		switch rule {
		case sharedPoolMutationUpdateVCPUsLimit,
			sharedPoolMutationUpdateTags,
			sharedPoolMutationDeleteInstance,
			sharedPoolMutationDeletePool:
			return nil
		case sharedPoolMutationBlockedWhilePaused, sharedPoolMutationCreateTiDBCluster:
			return resourcePoolStatusError(codes.FailedPrecondition, reasonPauseResumeInvalidState,
				"shared pool does not accept this mutation while paused")
		default:
			return status.Errorf(codes.Internal, "unknown shared pool mutation rule %d", rule)
		}
	default:
		return resourcePoolStatusError(codes.FailedPrecondition, reasonPauseResumeInvalidState,
			fmt.Sprintf("unknown shared pool pause/resume state %q", sharedPool.PauseResumeState))
	}
}

// SharedPoolAllowsOrdinaryMutation lets background jobs use the same
// lifecycle admission rule as server handlers without exposing rule details.
func SharedPoolAllowsOrdinaryMutation(sharedPool *entity.SharedPool) bool {
	return guardSharedPoolMutation(sharedPool, sharedPoolMutationBlockedWhilePaused) == nil
}

func (s *serverImpl) guardSharedPoolMutationByID(ctx context.Context, sharedPoolID string, rule sharedPoolMutationRule) error {
	if s.sharedPoolRepo == nil {
		return status.Error(codes.Internal, "shared pool repository is not initialized")
	}
	sharedPool, err := s.sharedPoolRepo.Load(ctx, sharedPoolID, false)
	if err != nil {
		return err
	}
	return guardSharedPoolMutation(sharedPool, rule)
}

// guardSharedPoolCleanupMutationByID applies Pause/Resume admission to a
// living pool while allowing retained data to be cleaned up after the pool has
// been soft-deleted.
func (s *serverImpl) guardSharedPoolCleanupMutationByID(ctx context.Context, sharedPoolID string) (*entity.SharedPool, error) {
	if s.sharedPoolRepo == nil {
		return nil, status.Error(codes.Internal, "shared pool repository is not initialized")
	}
	sharedPool, err := s.sharedPoolRepo.Load(ctx, sharedPoolID, true)
	if err != nil {
		return nil, err
	}
	if sharedPool == nil {
		return nil, status.Error(codes.Internal, "shared pool is nil")
	}
	if sharedPool.DeletedAt != nil {
		return sharedPool, nil
	}
	if err := guardSharedPoolMutation(sharedPool, sharedPoolMutationBlockedWhilePaused); err != nil {
		return nil, err
	}
	return sharedPool, nil
}

func (s *serverImpl) guardTiDBClusterMutationByID(ctx context.Context, clusterID string, rule sharedPoolMutationRule) error {
	if s.tidbClusterRepo == nil {
		return status.Error(codes.Internal, "TiDB cluster repository is not initialized")
	}
	cluster, err := s.tidbClusterRepo.Load(ctx, clusterID, false)
	if err != nil {
		return err
	}
	return s.guardSharedPoolMutationByID(ctx, cluster.GetSharedPoolId(), rule)
}
