package serviceaccount

import (
	"context"
	"fmt"
	"time"

	serviceaccountstore "github.com/tidbcloud/AgentTeam/internal/store/serviceaccount"
	"github.com/tidbcloud/AgentTeam/internal/workspace/owner"
)

// deliverOnePlatformAccountDeletion applies one deleted platform-account
// projection on the process that currently owns its workspace. The store lease
// makes the observer retryable across process crashes; the observer itself is
// required to be idempotent because its effect and the delivery acknowledgement
// cannot be committed atomically.
func (service *PlatformService) deliverOnePlatformAccountDeletion(ctx context.Context, now time.Time) (delivered bool, retried bool, err error) {
	if service.deletionObserver == nil {
		return false, false, nil
	}

	afterID := service.workerCursor(platformWorkerCursorDeletion)
	candidates, err := service.accounts.ListDuePlatformDeletionDeliveries(ctx, now, afterID, platformProcessBatchSize)
	if err != nil {
		return false, false, fmt.Errorf("list due service account deletion deliveries: %w", err)
	}
	for _, candidate := range candidates {
		service.advanceDeletionCursor(candidate.ID, len(candidates) == platformProcessBatchSize)
		if !owner.IsManaged(service.managedWorkspaces, candidate.WorkspaceID) {
			continue
		}

		delivery, leased, err := service.accounts.LeasePlatformDeletionDelivery(
			ctx,
			candidate.ID,
			now,
			platformDeletionDeliveryLeaseDuration,
		)
		if err != nil {
			return false, false, fmt.Errorf("lease service account deletion delivery: %w", err)
		}
		if !leased {
			continue
		}

		// Placement may move after candidate selection. Fence again after the
		// lease and release the row through the normal retry path rather than
		// executing owner-local runtime effects from the old process.
		if !owner.IsManaged(service.managedWorkspaces, delivery.WorkspaceID) {
			cause := owner.OwnerMismatchError{WorkspaceID: delivery.WorkspaceID}
			if markErr := service.accounts.MarkPlatformDeletionDeliveryFailed(
				ctx,
				delivery.ID,
				delivery.LeaseToken,
				cause,
				now,
				platformDeletionDeliveryMaxBackoff,
			); markErr != nil {
				return false, false, fmt.Errorf("release moved service account deletion delivery: %w", markErr)
			}
			return false, true, nil
		}

		account := localAccountFromPlatformDeletionDelivery(delivery)
		if observerErr := notifyLocalAccountsDeleted(ctx, service.deletionObserver, []serviceaccountstore.Account{account}); observerErr != nil {
			if markErr := service.accounts.MarkPlatformDeletionDeliveryFailed(
				ctx,
				delivery.ID,
				delivery.LeaseToken,
				observerErr,
				now,
				platformDeletionDeliveryMaxBackoff,
			); markErr != nil {
				return false, false, fmt.Errorf("record service account deletion delivery failure: %w", markErr)
			}
			return false, true, nil
		}
		if err := service.accounts.MarkPlatformDeletionDeliveryDelivered(ctx, delivery.ID, delivery.LeaseToken, now); err != nil {
			return false, false, fmt.Errorf("acknowledge service account deletion delivery: %w", err)
		}
		return true, false, nil
	}

	if len(candidates) < platformProcessBatchSize {
		service.setWorkerCursor(platformWorkerCursorDeletion, "")
	}
	return false, false, nil
}

func (service *PlatformService) advanceDeletionCursor(deliveryID string, fullPage bool) {
	if fullPage {
		service.setWorkerCursor(platformWorkerCursorDeletion, deliveryID)
		return
	}
	service.setWorkerCursor(platformWorkerCursorDeletion, "")
}

func localAccountFromPlatformDeletionDelivery(delivery serviceaccountstore.PlatformDeletionDelivery) serviceaccountstore.Account {
	managementKind := serviceaccountstore.ManagementKindWorkspace
	if delivery.PlatformServiceAccountID != "" {
		managementKind = serviceaccountstore.ManagementKindPlatform
	}
	return serviceaccountstore.Account{
		ID:                       delivery.LocalServiceAccountID,
		PrincipalID:              delivery.LocalServiceAccountPrincipalID,
		WorkspaceID:              delivery.WorkspaceID,
		ManagementKind:           managementKind,
		PlatformServiceAccountID: delivery.PlatformServiceAccountID,
		MessageScopeID:           delivery.MessageScopeID,
	}
}
