package agentstate

import (
	"errors"
	"fmt"
	"strings"
)

var (
	ErrInvalidTransition      = errors.New("invalid agent state transition")
	ErrTerminalWake           = fmt.Errorf("%w: terminal wake is immutable", ErrInvalidTransition)
	ErrWakeIdentityChanged    = fmt.Errorf("%w: wake identity changed", ErrInvalidTransition)
	ErrAttemptIdentityChanged = fmt.Errorf("%w: execution attempt identity changed", ErrInvalidTransition)
)

// AdvanceWakeDelivery applies the canonical delivery lifecycle. A delivery is
// received first and then moves directly to a server-confirmed visible reply,
// no-reply terminal, or failed terminal. There is no emitterless intermediate
// delivery phase.
func AdvanceWakeDelivery(current WakeDeliveryState, next WakeDeliveryState) (WakeDeliveryState, error) {
	if err := current.Validate(); err != nil {
		return WakeDeliveryState{}, fmt.Errorf("validate current wake: %w", err)
	}
	if err := next.Validate(); err != nil {
		return WakeDeliveryState{}, fmt.Errorf("%w: validate next wake: %v", ErrInvalidTransition, err)
	}
	if wakeIsTerminal(current.Phase) {
		return WakeDeliveryState{}, ErrTerminalWake
	}
	if !sameWakeIdentity(current, next) {
		return WakeDeliveryState{}, ErrWakeIdentityChanged
	}
	if !wakeTransitionAllowed(current.Phase, next.Phase) {
		return WakeDeliveryState{}, fmt.Errorf(
			"%w: wake %s -> %s",
			ErrInvalidTransition,
			current.Phase,
			next.Phase,
		)
	}
	return cloneWakeDelivery(next), nil
}

func wakeTransitionAllowed(current, next WakeDeliveryPhase) bool {
	switch current {
	case WakePhaseReceived:
		return next == WakePhaseReceived ||
			next == WakePhaseReplyConfirmed ||
			next == WakePhaseNoReply ||
			next == WakePhaseFailed
	default:
		return false
	}
}

func wakeIsTerminal(phase WakeDeliveryPhase) bool {
	return phase == WakePhaseReplyConfirmed ||
		phase == WakePhaseNoReply ||
		phase == WakePhaseFailed
}

func sameWakeIdentity(left, right WakeDeliveryState) bool {
	return strings.TrimSpace(left.LocalWakeID) == strings.TrimSpace(right.LocalWakeID) &&
		strings.TrimSpace(left.SourceMessageID) == strings.TrimSpace(right.SourceMessageID) &&
		strings.TrimSpace(left.TargetKey) == strings.TrimSpace(right.TargetKey) &&
		left.AckedAt.Equal(right.AckedAt)
}

func cloneWakeDelivery(state WakeDeliveryState) WakeDeliveryState {
	state.ReplyObjectIDs = append([]string(nil), state.ReplyObjectIDs...)
	return state
}

func ValidateExecutionTransition(current, next AgentExecutionState) error {
	if err := current.Validate(); err != nil {
		return fmt.Errorf("validate current execution: %w", err)
	}
	if err := next.Validate(); err != nil {
		return fmt.Errorf("validate next execution: %w", err)
	}
	if !executionTransitionAllowed(current.Phase, next.Phase) {
		return fmt.Errorf(
			"%w: execution %s -> %s",
			ErrInvalidTransition,
			current.Phase,
			next.Phase,
		)
	}
	if executionCarriesAttempt(current.Phase) && executionCarriesAttempt(next.Phase) {
		currentAttempt := strings.TrimSpace(current.AttemptToken)
		nextAttempt := strings.TrimSpace(next.AttemptToken)
		if currentAttempt != "" && nextAttempt != "" && currentAttempt != nextAttempt {
			return ErrAttemptIdentityChanged
		}
	}
	return nil
}

func executionTransitionAllowed(current, next ExecutionPhase) bool {
	switch current {
	case ExecutionIdle:
		return next == ExecutionIdle || next == ExecutionRunning
	case ExecutionRunning:
		return next == ExecutionRunning ||
			next == ExecutionRetryWait ||
			next == ExecutionRecovering ||
			next == ExecutionFailed ||
			next == ExecutionIdle
	case ExecutionRetryWait:
		return next == ExecutionRetryWait ||
			next == ExecutionRunning ||
			next == ExecutionRecovering ||
			next == ExecutionFailed
	case ExecutionRecovering:
		return next == ExecutionRecovering ||
			next == ExecutionRunning ||
			next == ExecutionFailed ||
			next == ExecutionIdle
	case ExecutionFailed:
		return next == ExecutionFailed ||
			next == ExecutionRunning ||
			next == ExecutionIdle
	default:
		return false
	}
}

func executionCarriesAttempt(phase ExecutionPhase) bool {
	return phase != ExecutionIdle
}
