package pendingactions

import (
	"context"
	"fmt"
	"sync"

	portainer "github.com/portainer/portainer/api"
	"github.com/portainer/portainer/api/dataservices"
	"github.com/portainer/portainer/api/internal/authorization"
	kubecli "github.com/portainer/portainer/api/kubernetes/cli"
	"github.com/rs/zerolog/log"
)

type (
	PendingActionsService struct {
		authorizationService *authorization.Service
		clientFactory        *kubecli.ClientFactory
		dataStore            dataservices.DataStore
		shutdownCtx          context.Context

		mu sync.Mutex
	}
)

func NewService(
	dataStore dataservices.DataStore,
	clientFactory *kubecli.ClientFactory,
	authorizationService *authorization.Service,
	shutdownCtx context.Context,
) *PendingActionsService {
	return &PendingActionsService{
		dataStore:            dataStore,
		shutdownCtx:          shutdownCtx,
		authorizationService: authorizationService,
		clientFactory:        clientFactory,
		mu:                   sync.Mutex{},
	}
}

func (service *PendingActionsService) Create(r portainer.PendingActions) error {
	return service.dataStore.PendingActions().Create(&r)
}

func (service *PendingActionsService) Execute(id portainer.EndpointID) error {

	service.mu.Lock()
	defer service.mu.Unlock()

	endpoint, err := service.dataStore.Endpoint().Endpoint(id)
	if err != nil {
		return fmt.Errorf("failed to retrieve endpoint %d: %w", id, err)
	}

	if endpoint.Status != portainer.EndpointStatusUp {
		log.Debug().Msgf("Endpoint %d is not up", id)
		return fmt.Errorf("endpoint %d is not up: %w", id, err)
	}

	pendingActions, err := service.dataStore.PendingActions().ReadAll()
	if err != nil {
		log.Error().Err(err).Msgf("failed to retrieve pending actions")
		return fmt.Errorf("failed to retrieve pending actions for endpoint %d: %w", id, err)
	}

	for _, endpointPendingAction := range pendingActions {
		if endpointPendingAction.EndpointID == id {
			err := service.executePendingAction(endpointPendingAction, endpoint)
			if err != nil {
				log.Error().Err(err).Msgf("failed to execute pending action")
				return fmt.Errorf("failed to execute pending action: %w", err)
			} else {
				// delete the pending action
				err := service.dataStore.PendingActions().Delete(endpointPendingAction.ID)
				if err != nil {
					log.Error().Err(err).Msgf("failed to delete pending action")
					return fmt.Errorf("failed to delete pending action: %w", err)
				}
			}
		}
	}

	return nil
}

func (service *PendingActionsService) executePendingAction(pendingAction portainer.PendingActions, endpoint *portainer.Endpoint) error {
	log.Debug().Msgf("Executing pending action %s for endpoint %d", pendingAction.Action, pendingAction.EndpointID)

	defer func() {
		log.Debug().Msgf("End executing pending action %s for endpoint %d", pendingAction.Action, pendingAction.EndpointID)
	}()

	switch pendingAction.Action {
	case "CleanNAPWithOverridePolicies":
		if (pendingAction.ActionData == nil) || (pendingAction.ActionData.(portainer.EndpointGroupID) == 0) {
			service.authorizationService.CleanNAPWithOverridePolicies(service.dataStore, endpoint, nil)
		} else {
			endpointGroupID := pendingAction.ActionData.(portainer.EndpointGroupID)
			endpointGroup, err := service.dataStore.EndpointGroup().Read(portainer.EndpointGroupID(endpointGroupID))
			if err != nil {
				log.Error().Err(err).Msgf("Error reading endpoint group to clean NAP with override policies for endpoint %d and endpoint group %d", endpoint.ID, endpointGroup.ID)
				return fmt.Errorf("failed to retrieve endpoint group %d: %w", endpointGroupID, err)
			}
			err = service.authorizationService.CleanNAPWithOverridePolicies(service.dataStore, endpoint, endpointGroup)
			if err != nil {
				log.Error().Err(err).Msgf("Error cleaning NAP with override policies for endpoint %d and endpoint group %d", endpoint.ID, endpointGroup.ID)
				return fmt.Errorf("failed to clean NAP with override policies for endpoint %d and endpoint group %d: %w", endpoint.ID, endpointGroup.ID, err)
			}
		}
		return nil
	}
	return nil
}