package orchestrator

import (
	"context"
	"encoding/json"
	"errors"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	workflowpayload "github.com/tidbcloud/AgentTeam/internal/workflow/payload"
	"github.com/tidbcloud/AgentTeam/internal/workflow/plan"
)

const (
	CallbackActionGetCurrent = "get_current"
	CallbackActionComplete   = "complete"
	CallbackActionFail       = "fail"
	// CallbackActionPause is a non-terminal callback action: the effect stays
	// awaiting, automatic recovery is durably stopped, and no resume trigger
	// exists. It is the renamed permanent recovery pause: later Agent activity
	// never unpauses it; only callback complete/fail, cancel, or terminal
	// lifecycle ends/cleans it. It accepts no output, workScope, workItems,
	// failure, or idempotencyKey.
	CallbackActionPause = "pause"
	// CallbackActionPark is a non-terminal callback action: it arms the durable
	// park FSM (arm_on_idle) so automatic recovery stops while the responder is
	// idle. The first authoritative known idle confirms parked; the first
	// authoritative known working clears the park and resets the recovery
	// schedule. It accepts no output, workScope, workItems, failure, or
	// idempotencyKey.
	CallbackActionPark = "park"
)

var (
	ErrCallbackNotFound        = errors.New("workflow Run callback not found")
	ErrCallbackUnauthorized    = errors.New("workflow Run callback is not authorized for this Agent")
	ErrCallbackNotReady        = errors.New("workflow Run callback delivery is not ready")
	ErrCallbackNotWaiting      = errors.New("workflow Run callback is no longer waiting")
	ErrCallbackAlreadyResolved = errors.New("workflow Run callback was already resolved with a different result")
	ErrCallbackOutputInvalid   = errors.New("workflow Run callback output is invalid")
	ErrCallbackReplayConflict  = errors.New("workflow Run callback replay conflicts with the committed result")
	ErrCallbackPayloadChanged  = errors.New("workflow callback payload snapshot changed")
	ErrRunTerminal             = errors.New("workflow Run is terminal")
)

// CallbackCommand contains authenticated workspace/Agent identity plus
// caller-selected run/effect IDs and the business result. RunID and CallbackID
// are untrusted selectors; CallbackService verifies them against the frozen
// responder receipt before exposing or mutating callback state.
type CallbackCommand struct {
	WorkspaceID    string
	RunID          string
	CallbackID     string
	AgentID        string
	MemberID       string
	Action         string
	Section        string
	Path           string
	Cursor         string
	Limit          int
	Output         json.RawMessage
	WorkScope      *domain.MessageScopeValue
	WorkItems      json.RawMessage
	FailureCode    string
	FailureMessage string
	IdempotencyKey string
}

type CallbackListCommand struct {
	WorkspaceID string
	AgentID     string
	MemberID    string
	Cursor      string
	Limit       int
}

type CallbackListResult struct {
	Steps      []CallbackResult `json:"steps"`
	NextCursor string           `json:"nextCursor,omitempty"`
}

type CallbackPayloadRead struct {
	RunID      string               `json:"runId"`
	CallbackID string               `json:"callbackId"`
	Status     string               `json:"status"`
	RunStatus  string               `json:"runStatus"`
	Page       workflowpayload.Page `json:"page"`
}

type CallbackResult struct {
	RunID             string                     `json:"runId"`
	CallbackID        string                     `json:"callbackId"`
	ActivationID      string                     `json:"activationId"`
	NodeID            string                     `json:"nodeId"`
	Status            string                     `json:"status"`
	RunStatus         string                     `json:"runStatus"`
	Input             json.RawMessage            `json:"input,omitempty"`
	OutputContract    json.RawMessage            `json:"outputContract,omitempty"`
	WorkItemsContract *CallbackWorkItemsContract `json:"workItemsContract,omitempty"`
	InputRef          *workflowpayload.Ref       `json:"inputRef,omitempty"`
	OutputContractRef *workflowpayload.Ref       `json:"outputContractRef,omitempty"`
	WorkItemsRef      *workflowpayload.Ref       `json:"workItemsRef,omitempty"`
	DeliveryScope     *domain.MessageScopeValue  `json:"deliveryScope,omitempty"`
	// WorkScope is omitted for legacy artifacts, null while a collaboration
	// callback asks the Agent to choose its working scope, and an object for a
	// fixed scope.
	WorkScope     json.RawMessage             `json:"workScope,omitempty"`
	RelatedScopes *[]domain.MessageScopeValue `json:"relatedScopes,omitempty"`
	Replayed      bool                        `json:"replayed"`
	// TeamMemberContext is the server-frozen minimal Workflow-specific member
	// context for the callback responder of any Run with a frozen Team ID
	// (Team-linked or dynamic-explicit). It carries only the responder's own
	// Team membership (identity, role label, role overlay) plus minimal Team
	// identity/purpose. It never includes the team roster, other members'
	// duties, or wake configuration.
	TeamMemberContext *CallbackTeamMemberContext `json:"teamMemberContext,omitempty"`
}

// CallbackTeamMemberContext is the minimal hidden context a Workflow Step
// responder receives for a Run with a frozen Team ID. It is resolved
// server-side from the frozen Run Team ID and the frozen callback responder, so
// it can never be forged from visible message text or a callback request. The
// full team roster is intentionally excluded.
type CallbackTeamMemberContext struct {
	TeamID          string `json:"teamId"`
	TeamHandle      string `json:"teamHandle,omitempty"`
	TeamDisplayName string `json:"teamDisplayName,omitempty"`
	TeamPurpose     string `json:"teamPurpose,omitempty"`
	TeamMemberID    string `json:"teamMemberId"`
	Role            string `json:"role,omitempty"`
	RoleLabel       string `json:"roleLabel,omitempty"`
	RoleOverlay     string `json:"roleOverlay,omitempty"`
}

// cloneCallbackTeamMemberContext returns a defensive copy so the frozen snapshot
// persisted on the effect receipt cannot be mutated through a later reference.
func cloneCallbackTeamMemberContext(context *CallbackTeamMemberContext) *CallbackTeamMemberContext {
	if context == nil {
		return nil
	}
	cloned := *context
	return &cloned
}

type CallbackWorkItemsContract struct {
	Required                  bool     `json:"required"`
	MaxItems                  int      `json:"maxItems"`
	MaxKeyBytes               int      `json:"maxKeyBytes"`
	MaxRelatedScopesPerItem   int      `json:"maxRelatedScopesPerItem"`
	MaxDependencyEdges        int      `json:"maxDependencyEdges"`
	MaxRelatedScopeReferences int      `json:"maxRelatedScopeReferences"`
	MaxJSONValues             int      `json:"maxJsonValues"`
	MaxJSONBytes              int      `json:"maxJsonBytes"`
	AllowedResponders         []string `json:"allowedResponders"`
	// DependencyValueContract is the exact ArtifactContract for the object
	// stored at dependencies.<key>.value. Planner-authored reference paths are
	// relative to this contract (for example, $op08.result).
	DependencyValueContract plan.ArtifactContract `json:"dependencyValueContract"`
}

// CallbackRuntime is implemented by the durable Run service. It must verify
// the exact callback attempt, the authenticated Agent against the separately
// frozen callback authority, terminal fencing, output contract and same-result
// replay semantics in one transaction. Other ordinary wake recipients do not
// gain callback authority.
type CallbackRuntime interface {
	ExecuteCallback(context.Context, CallbackCommand) (CallbackResult, error)
	ListCallbacks(context.Context, CallbackListCommand) (CallbackListResult, error)
}

// CallbackPayloadReader is an additive capability for explicit lossless reads;
// legacy test/runtime adapters can continue to implement CallbackRuntime while
// the production CallbackService owns the authorized section path.
type CallbackPayloadReader interface {
	ReadCallbackSection(context.Context, CallbackCommand) (CallbackPayloadRead, error)
}
