package promptcompiler

import (
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
)

func validateRuntimeInputEnvelope(input RuntimeInputEnvelope) error {
	hasBundle := input.Bundle != nil
	hasDelivery := input.Delivery != nil

	if strings.TrimSpace(input.EventID) == "" {
		return invalidRuntimeInputError(input, "event id is required")
	}
	payloads := 0
	if hasBundle {
		payloads++
	}
	if hasDelivery {
		payloads++
	}
	if payloads != 1 {
		return invalidRuntimeInputError(input, "exactly one of bundle or delivery must be set")
	}

	switch input.EventKind {
	case EventThreadContract:
		if strings.TrimSpace(input.TurnID) != "" {
			return invalidRuntimeInputError(input, "thread contract compile must not set turn id")
		}
		if !hasBundle {
			return invalidRuntimeInputError(input, fmt.Sprintf("event kind %q requires bundle", EventThreadContract))
		}
		if !isThreadContractEventType(input.EventType) {
			return invalidRuntimeInputError(input, fmt.Sprintf("event kind %q cannot use event type %q", EventThreadContract, input.EventType))
		}
	case EventTurnContext:
		if !hasBundle {
			return invalidRuntimeInputError(input, fmt.Sprintf("event kind %q requires bundle", EventTurnContext))
		}
		if !isTurnContextEventType(input.EventType) {
			return invalidRuntimeInputError(input, fmt.Sprintf("event kind %q cannot use event type %q", EventTurnContext, input.EventType))
		}
	case EventDeliveryEnvelope:
		if !hasDelivery {
			return invalidRuntimeInputError(input, fmt.Sprintf("event kind %q requires delivery", EventDeliveryEnvelope))
		}
		if !isDeliveryEnvelopeEventType(input.EventType) {
			return invalidRuntimeInputError(input, fmt.Sprintf("event kind %q cannot use event type %q", EventDeliveryEnvelope, input.EventType))
		}
	default:
		return invalidRuntimeInputError(input, fmt.Sprintf("unsupported event kind %q", input.EventKind))
	}

	if input.Bundle != nil {
		if err := validatePromptBundle(input, *input.Bundle); err != nil {
			return err
		}
	}
	if input.Delivery != nil {
		if err := validateDeliveryEnvelope(input, *input.Delivery); err != nil {
			return err
		}
	}
	return nil
}

func validatePromptBundle(input RuntimeInputEnvelope, bundle PromptBundle) error {
	if strings.TrimSpace(bundle.EventID) != strings.TrimSpace(input.EventID) {
		return invalidRuntimeInputError(input, fmt.Sprintf("bundle event id %q does not match envelope event id %q", bundle.EventID, input.EventID))
	}
	if bundle.EventType != input.EventType {
		return invalidRuntimeInputError(input, fmt.Sprintf("bundle event type %q does not match envelope event type %q", bundle.EventType, input.EventType))
	}
	if strings.TrimSpace(bundle.TurnID) != strings.TrimSpace(input.TurnID) {
		return invalidRuntimeInputError(input, fmt.Sprintf("bundle turn id %q does not match envelope turn id %q", bundle.TurnID, input.TurnID))
	}
	if strings.TrimSpace(bundle.AgentID) != strings.TrimSpace(input.AgentID) {
		return invalidRuntimeInputError(input, fmt.Sprintf("bundle agent id %q does not match envelope agent id %q", bundle.AgentID, input.AgentID))
	}
	for _, block := range bundle.Blocks {
		if err := registeredBlockIdentityError(block); err != nil {
			return invalidRuntimeInputError(input, err.Error())
		}
	}
	return nil
}

func validateDeliveryEnvelope(input RuntimeInputEnvelope, delivery DeliveryEnvelope) error {
	agentID := strings.TrimSpace(delivery.Correlation.AgentID)
	if agentID != "" && agentID != strings.TrimSpace(input.AgentID) {
		return invalidRuntimeInputError(input, fmt.Sprintf("delivery correlation agent id %q does not match envelope agent id %q", delivery.Correlation.AgentID, input.AgentID))
	}
	turnID := strings.TrimSpace(input.TurnID)
	activeTurnID := strings.TrimSpace(delivery.Transport.ActiveTurnID)
	switch {
	case turnID != "" && activeTurnID == "":
		return invalidRuntimeInputError(input, fmt.Sprintf("delivery transport active turn id must match envelope turn id %q", input.TurnID))
	case turnID != "" && activeTurnID != turnID:
		return invalidRuntimeInputError(input, fmt.Sprintf("delivery transport active turn id %q does not match envelope turn id %q", delivery.Transport.ActiveTurnID, input.TurnID))
	}

	transportWakeToken := strings.TrimSpace(delivery.Transport.WakeToken)
	correlationWakeToken := strings.TrimSpace(delivery.Correlation.WakeToken)
	if transportWakeToken != correlationWakeToken {
		return invalidRuntimeInputError(input, fmt.Sprintf("delivery correlation wake token %q does not match transport wake token %q", delivery.Correlation.WakeToken, delivery.Transport.WakeToken))
	}
	if delivery.WorkflowContext.Available {
		rawWorkflowMetadata := []string{
			types.WorkflowContextMetadataWorkspaceIDKey,
			types.WorkflowContextMetadataPlanIDKey,
			types.WorkflowContextMetadataPlanVersionIDKey,
			types.WorkflowContextMetadataRunIDKey,
			types.WorkflowContextMetadataEffectIDKey,
			types.WorkflowContextMetadataNodeIDKey,
			types.WorkflowContextMetadataSourceKindKey,
			types.WorkflowContextMetadataDeliveryScopeKey,
		}
		for _, key := range rawWorkflowMetadata {
			if _, ok := delivery.Metadata[key]; ok {
				return invalidRuntimeInputError(input, fmt.Sprintf("delivery metadata must not expose workflow context key %q", key))
			}
		}
	}

	switch delivery.SourceKind {
	case DeliverySourceRoutedMessage:
		if strings.TrimSpace(delivery.Correlation.MessageID) == "" {
			return invalidRuntimeInputError(input, "routed-message delivery must include correlation message id")
		}
		if strings.TrimSpace(delivery.MessageType) == "reaction" {
			return invalidRuntimeInputError(input, `routed-message delivery cannot use message type "reaction"`)
		}
	case DeliverySourceReaction:
		if strings.TrimSpace(delivery.Correlation.DeliveryID) == "" {
			return invalidRuntimeInputError(input, "reaction delivery must include correlation delivery id")
		}
		if strings.TrimSpace(delivery.MessageType) != "reaction" {
			return invalidRuntimeInputError(input, fmt.Sprintf("reaction delivery must use message type %q, got %q", "reaction", delivery.MessageType))
		}
	default:
		return invalidRuntimeInputError(input, fmt.Sprintf("unsupported delivery source kind %q", delivery.SourceKind))
	}

	switch input.EventType {
	case EventRoutedMessageDelivery:
		if delivery.SourceKind != DeliverySourceRoutedMessage {
			return invalidRuntimeInputError(input, fmt.Sprintf("event type %q must use delivery source kind %q, got %q", EventRoutedMessageDelivery, DeliverySourceRoutedMessage, delivery.SourceKind))
		}
	case EventReactionDelivery:
		if delivery.SourceKind != DeliverySourceReaction {
			return invalidRuntimeInputError(input, fmt.Sprintf("event type %q must use delivery source kind %q, got %q", EventReactionDelivery, DeliverySourceReaction, delivery.SourceKind))
		}
	}
	return nil
}
func invalidRuntimeInputError(input RuntimeInputEnvelope, reason string) error {
	return &PromptCompileError{
		Kind:      ErrInvalidRuntimeInput,
		EventID:   strings.TrimSpace(input.EventID),
		EventType: input.EventType,
		Reason:    reason,
	}
}

func isThreadContractEventType(eventType CompileEventType) bool {
	return eventType == EventThreadStart || eventType == EventThreadResume
}

func isTurnContextEventType(eventType CompileEventType) bool {
	switch eventType {
	case EventFreshStart, EventWake, EventResume, EventDeferredContinuation, EventFinalVisibleReplyCheck:
		return true
	default:
		return false
	}
}

func isDeliveryEnvelopeEventType(eventType CompileEventType) bool {
	return eventType == EventRoutedMessageDelivery || eventType == EventReactionDelivery
}
