package psvc

import (
	"context"
	"strings"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/oobs"
	"go.uber.org/zap"
)

func (s *svc) recomputeAggregateState(ctx context.Context, payment *agg.Payment) (bool, error) {
	logger := s.logger
	if payment == nil {
		return false, nil
	}
	current := payment.State
	target := s.deriveAggregateTarget(payment)
	next, changed, err := s.transitionAggregateState(current, target)
	if err != nil {
		return false, err
	}
	if !changed {
		return false, nil
	}
	payment.State = next
	logger.Debug("Recomputed payment state from step executions",
		zap.String("payment_ref", strings.TrimSpace(payment.PaymentRef)),
		zap.String("from_state", string(current)),
		zap.String("to_state", string(next)),
		zap.Uint64("version", payment.Version),
	)
	if next == agg.StateSettled || next == agg.StateNeedsAttention || next == agg.StateFailed {
		logger.Debug("Payment entered finalization state",
			zap.String("payment_ref", strings.TrimSpace(payment.PaymentRef)),
			zap.String("state", string(next)),
			zap.Uint64("version", payment.Version),
		)
	}
	return true, s.observer.RecordPayment(ctx, oobs.RecordPaymentInput{
		Payment: payment,
		Event:   paymentEventForState(next),
	})
}

func (s *svc) deriveAggregateTarget(payment *agg.Payment) agg.State {
	if payment == nil {
		return agg.StateUnspecified
	}
	if s.state.IsAggregateTerminal(payment.State) {
		return payment.State
	}
	if len(payment.StepExecutions) == 0 {
		return agg.StateCreated
	}

	allDone := true
	for i := range payment.StepExecutions {
		step := payment.StepExecutions[i]
		switch step.State {
		case agg.StepStateNeedsAttention:
			return agg.StateNeedsAttention
		case agg.StepStateFailed:
			if step.Attempt >= s.maxAttemptsForStep(step.StepRef) {
				return agg.StateNeedsAttention
			}
			allDone = false
		case agg.StepStateCompleted, agg.StepStateSkipped:
		default:
			allDone = false
		}
	}
	if allDone {
		return agg.StateSettled
	}
	return agg.StateExecuting
}

func (s *svc) transitionAggregateState(current, target agg.State) (agg.State, bool, error) {
	if current == target {
		return current, false, nil
	}
	if s.state.IsAggregateTerminal(current) {
		return current, false, nil
	}
	if err := s.state.EnsureAggregateTransition(current, target); err == nil {
		return target, true, nil
	}
	if current == agg.StateCreated {
		if err := s.state.EnsureAggregateTransition(current, agg.StateExecuting); err == nil {
			current = agg.StateExecuting
		}
	}
	if current == agg.StateNeedsAttention && target == agg.StateCreated {
		if err := s.state.EnsureAggregateTransition(current, agg.StateExecuting); err == nil {
			current = agg.StateExecuting
		}
	}
	if current == target {
		return current, true, nil
	}
	if s.state.EnsureAggregateTransition(current, target) != nil {
		return current, false, nil //nolint:nilerr // invalid transition is treated as a no-op state update
	}
	return target, true, nil
}

func paymentEventForState(state agg.State) oobs.PaymentEvent {
	switch state {
	case agg.StateNeedsAttention:
		return oobs.PaymentEventNeedsAttention
	case agg.StateSettled:
		return oobs.PaymentEventSettled
	case agg.StateFailed:
		return oobs.PaymentEventFailed
	default:
		return oobs.PaymentEventStateChanged
	}
}

func (s *svc) maxAttemptsForStep(stepRef string) uint32 {
	if s.retryPolicy.MaxAttemptsByStepRef != nil {
		if maxAttempts := s.retryPolicy.MaxAttemptsByStepRef[stepRef]; maxAttempts > 0 {
			return maxAttempts
		}
	}
	if s.retryPolicy.MaxAttempts > 0 {
		return s.retryPolicy.MaxAttempts
	}
	return 1
}
