package openai

import (
	"context"
	"errors"
	"sync"
	"time"

	edgeservice "iop/apps/edge/internal/service"
)

// SingleRequestExecutor is the concurrent, request-safe composite executor
// driving the Plan -> Work -> Review lifecycle behind the single-request interface.
type SingleRequestExecutor struct {
	provider *singleRequestProviderStage
	bridge   *singleRequestWorkToolBridge
	plan     *singleRequestPlanStage
	work     *singleRequestWorkStage
	review   *singleRequestReviewStage
}

// NewSingleRequestExecutor constructs a production composite single-request executor
// backed by private stage drivers and the correlated continuation bridge.
func NewSingleRequestExecutor(service edgeserviceRunner) *SingleRequestExecutor {
	provider := newSingleRequestProviderStage(service)
	bridge := newSingleRequestWorkToolBridge()
	return &SingleRequestExecutor{
		provider: provider,
		bridge:   bridge,
		plan:     newSingleRequestPlanStage(provider, bridge),
		work:     newSingleRequestWorkStage(provider, bridge),
		review:   newSingleRequestReviewStage(provider, bridge),
	}
}

// ExecuteSingleRequest executes Plan -> Work -> Review sequentially for one request
// against a single controller and immutable binding.
func (s *SingleRequestExecutor) ExecuteSingleRequest(ctx context.Context, req edgeservice.SingleRequestRequest, ctrl edgeservice.SingleRequestController) error {
	if s == nil || s.provider == nil || s.bridge == nil || s.plan == nil || s.work == nil || s.review == nil || ctrl == nil {
		return edgeservice.ErrSingleRequestExecutorUnavailable
	}
	if req.RequestID == "" || req.RequestID != ctrl.RequestID() {
		return edgeservice.ErrSingleRequestIdentityMismatch
	}
	binding := ctrl.Binding()
	if binding == nil || req.Binding == nil {
		return edgeservice.ErrSingleRequestInvalidBinding
	}
	if binding.Workspace == nil || binding.Workspace.NodeID == "" {
		return edgeservice.ErrSingleRequestInvalidBinding
	}

	defer s.bridge.clearRequest(req.RequestID)
	quality := newSingleRequestQualityGate()

	seqCtrl := &singleRequestSequenceController{
		SingleRequestController: ctrl,
	}

	nodeRef := binding.Workspace.NodeID

	// Stage 1: Plan
	planReq := singleRequestPlanStageRequest{
		RequestID:        req.RequestID,
		Task:             req.Prompt,
		StageBinding:     binding.Plan,
		Limits:           binding.Limits,
		NodeRef:          nodeRef,
		SessionID:        req.RequestID,
		UsageAttribution: singleRequestUsageAttribution(binding.Plan),
		Sequence:         1,
		Quality:          quality,
	}
	_, err := s.plan.run(ctx, planReq, seqCtrl)
	if err != nil {
		return submitSingleRequestClosedTerminal(ctx, req.RequestID, seqCtrl, err)
	}

	// Stage 2: Work
	workReq := singleRequestWorkStageRequest{
		RequestID:        req.RequestID,
		Task:             req.Prompt,
		StageBinding:     binding.Work,
		Limits:           binding.Limits,
		NodeRef:          nodeRef,
		SessionID:        req.RequestID,
		UsageAttribution: singleRequestUsageAttribution(binding.Work),
		Sequence:         1,
		Quality:          quality,
	}
	if err := s.work.run(ctx, workReq, seqCtrl); err != nil {
		return submitSingleRequestClosedTerminal(ctx, req.RequestID, seqCtrl, err)
	}

	// Stage 3: Review
	reviewReq := singleRequestReviewStageRequest{
		RequestID:        req.RequestID,
		Task:             req.Prompt,
		StageBinding:     binding.Review,
		Limits:           binding.Limits,
		NodeRef:          nodeRef,
		SessionID:        req.RequestID,
		UsageAttribution: singleRequestUsageAttribution(binding.Review),
		Sequence:         1,
		Quality:          quality,
	}
	_, err = s.review.run(ctx, reviewReq, seqCtrl)
	if err != nil {
		return submitSingleRequestClosedTerminal(ctx, req.RequestID, seqCtrl, err)
	}

	return nil
}

func submitSingleRequestClosedTerminal(ctx context.Context, requestID string, ctrl edgeservice.SingleRequestController, stageErr error) error {
	// The parent request context is owned by the service. Returning its error
	// lets that owner distinguish caller cancellation from wall-clock budget
	// exhaustion without racing a stage terminal envelope.
	if ctx != nil {
		if err := ctx.Err(); err != nil {
			return err
		}
		if deadline, ok := ctx.Deadline(); ok && !time.Now().Before(deadline) {
			return context.DeadlineExceeded
		}
	}
	disposition, ok := singleRequestTerminalDisposition(stageErr)
	if !ok {
		switch {
		case errors.Is(stageErr, context.DeadlineExceeded):
			disposition = edgeservice.SingleRequestTerminalDisposition{Kind: edgeservice.SingleRequestTerminalError, ErrorClass: edgeservice.SingleRequestTerminalErrorTimeout}
		default:
			disposition = edgeservice.SingleRequestTerminalDisposition{Kind: edgeservice.SingleRequestTerminalError, ErrorClass: edgeservice.SingleRequestTerminalErrorProvider}
		}
	}

	var envelope edgeservice.SingleRequestEnvelope
	switch disposition.Kind {
	case edgeservice.SingleRequestTerminalLength:
		envelope = edgeservice.SingleRequestEnvelope{
			RequestID: requestID,
			Stage:     edgeservice.SingleRequestStateFinalizing,
			Result: &edgeservice.SingleRequestResult{
				Terminal: disposition,
			},
		}
	case edgeservice.SingleRequestTerminalCancelled:
		envelope = edgeservice.SingleRequestEnvelope{RequestID: requestID, Stage: edgeservice.SingleRequestStateCancelled, Terminal: &disposition}
	default:
		if disposition.Kind != edgeservice.SingleRequestTerminalError || disposition.Validate() != nil {
			disposition = edgeservice.SingleRequestTerminalDisposition{Kind: edgeservice.SingleRequestTerminalError, ErrorClass: edgeservice.SingleRequestTerminalErrorProvider}
		}
		envelope = edgeservice.SingleRequestEnvelope{
			RequestID: requestID,
			Stage:     edgeservice.SingleRequestStateFailed,
			Terminal:  &disposition,
			Err:       edgeservice.ErrSingleRequestFailed,
		}
	}
	if err := ctrl.SubmitEnvelope(envelope); err != nil && !errors.Is(err, edgeservice.ErrSingleRequestTerminal) {
		return err
	}
	return nil
}

// ContinueInternalTool delegates correlated tool continuation results to the
// request-safe bridge.
func (s *SingleRequestExecutor) ContinueInternalTool(ctx context.Context, result edgeservice.InternalWorkspaceToolResult) error {
	if s == nil || s.bridge == nil {
		return edgeservice.ErrSingleRequestExecutorUnavailable
	}
	return s.bridge.ContinueInternalTool(ctx, result)
}

type singleRequestSequenceController struct {
	edgeservice.SingleRequestController
	mu       sync.Mutex
	sequence uint64
}

func (c *singleRequestSequenceController) SubmitEnvelope(env edgeservice.SingleRequestEnvelope) error {
	c.mu.Lock()
	c.sequence++
	env.Sequence = c.sequence
	c.mu.Unlock()
	return c.SingleRequestController.SubmitEnvelope(env)
}

func singleRequestUsageAttribution(binding edgeservice.SingleRequestStageBinding) string {
	if binding.Dispatch != nil {
		return binding.Dispatch.PrincipalRef
	}
	return ""
}
