package serviceaccount

import (
	"context"
	"fmt"
	"time"
)

// ProcessOnce runs one bounded round of every control-plane and workspace-
// owned reconciliation phase. Callers may repeat until the returned result is
// empty; every phase is independently idempotent and crash-recoverable.
func (service *PlatformService) ProcessOnce(ctx context.Context) (PlatformProcessResult, error) {
	if service == nil || service.db == nil || service.accounts == nil || service.materializer == nil {
		return PlatformProcessResult{}, fmt.Errorf("platform service account service is not configured")
	}
	now := service.normalizedNow(time.Time{})
	var result PlatformProcessResult
	deleted, retried, err := service.deliverOnePlatformAccountDeletion(ctx, now)
	if err != nil {
		return result, err
	}
	if deleted {
		result.DeliveredPlatformDeletions = 1
	}
	if retried {
		result.RetriedPlatformDeletions = 1
	}
	if service.controlPlane {
		discovered, err := service.discoverOnePlatformProvisioningPage(ctx, now)
		if err != nil {
			return result, err
		}
		result.DiscoveredInputs = discovered
	}
	provisioned, err := service.processOnePlatformProvisioningInput(ctx, now)
	if err != nil {
		return result, err
	}
	if provisioned {
		result.ProvisionedInstances = 1
	}
	if service.controlPlane {
		advanced, err := service.advanceOnePlatformProvisioningGeneration(ctx, now)
		if err != nil {
			return result, err
		}
		if advanced {
			result.AdvancedAccounts = 1
		}
		allocated, err := service.allocateOnePlatformBroadcast(ctx, now)
		if err != nil {
			return result, err
		}
		if allocated {
			result.AllocatedBroadcasts = 1
		}
		targets, excluded, err := service.enumerateOnePlatformBroadcastPage(ctx, now)
		if err != nil {
			return result, err
		}
		result.EnumeratedDeliveries = targets
		result.SettledExcludedTargets = excluded
	}
	settled, err := service.settleOneExcludedPlatformDelivery(ctx, now)
	if err != nil {
		return result, err
	}
	if settled {
		result.SettledExcludedTargets++
	}
	delivered, retried, err := service.deliverOnePlatformBroadcast(ctx, now)
	if err != nil {
		return result, err
	}
	if delivered {
		result.DeliveredMessages = 1
	}
	if retried {
		result.RetriedDeliveries = 1
	}
	if service.controlPlane {
		completed, err := service.aggregateOnePlatformBroadcast(ctx, now)
		if err != nil {
			return result, err
		}
		if completed {
			result.CompletedBroadcasts = 1
		}
	}
	return result, nil
}

// Run continuously reconciles platform Service Account work until cancellation.
func (service *PlatformService) Run(ctx context.Context, interval time.Duration) {
	if interval <= 0 {
		interval = time.Second
	}
	ticker := time.NewTicker(interval)
	defer ticker.Stop()
	for {
		for {
			result, err := service.ProcessOnce(ctx)
			if err != nil || !result.hasWork() {
				break
			}
			select {
			case <-ctx.Done():
				return
			default:
			}
		}
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
		}
	}
}

func (result PlatformProcessResult) hasWork() bool {
	return result.DeliveredPlatformDeletions != 0 || result.RetriedPlatformDeletions != 0 ||
		result.DiscoveredInputs != 0 || result.ProvisionedInstances != 0 ||
		result.AdvancedAccounts != 0 || result.AllocatedBroadcasts != 0 ||
		result.EnumeratedDeliveries != 0 || result.SettledExcludedTargets != 0 ||
		result.DeliveredMessages != 0 || result.RetriedDeliveries != 0 ||
		result.CompletedBroadcasts != 0
}

type platformWorkerCursorKind int

const (
	platformWorkerCursorDeletion platformWorkerCursorKind = iota
	platformWorkerCursorProvisioning
	platformWorkerCursorDelivery
	platformWorkerCursorExcluded
)

func (service *PlatformService) workerCursor(kind platformWorkerCursorKind) string {
	service.cursorMu.Lock()
	defer service.cursorMu.Unlock()
	switch kind {
	case platformWorkerCursorDeletion:
		return service.deletionAfter
	case platformWorkerCursorDelivery:
		return service.deliveryAfter
	case platformWorkerCursorExcluded:
		return service.excludedAfter
	default:
		return service.provisioningAfter
	}
}

func (service *PlatformService) setWorkerCursor(kind platformWorkerCursorKind, value string) {
	service.cursorMu.Lock()
	defer service.cursorMu.Unlock()
	switch kind {
	case platformWorkerCursorDeletion:
		service.deletionAfter = value
	case platformWorkerCursorDelivery:
		service.deliveryAfter = value
	case platformWorkerCursorExcluded:
		service.excludedAfter = value
	default:
		service.provisioningAfter = value
	}
}
