package run

import (
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	runstore "github.com/tidbcloud/AgentTeam/internal/store/workflow/run"
	"github.com/tidbcloud/AgentTeam/internal/workflow/eventcontract"
)

// RunSummaryResponse converts one Run for a bounded collection response. It is
// exported for trusted Agent transports so human and Agent APIs cannot drift
// in redaction or payload shape.
func RunSummaryResponse(value runstore.Run) dto.WorkflowRunResponse {
	return runResponse(value, false)
}

// RunDetailResponse converts one Run for detail/control responses.
func RunDetailResponse(value runstore.Run) dto.WorkflowRunResponse {
	return runResponse(value, true)
}

func RunShowResponse(
	run runstore.Run,
	activations []runstore.Activation,
	effects []runstore.Effect,
) dto.WorkflowRunShowResponse {
	response := dto.WorkflowRunShowResponse{Run: runResponse(run, true)}
	var current *runstore.Activation
	for index := range activations {
		candidate := &activations[index]
		if current == nil || candidate.UpdatedAt.After(current.UpdatedAt) {
			current = candidate
		}
		if candidate.Status == runstore.ActivationStatusWaiting ||
			candidate.Status == runstore.ActivationStatusFailed {
			current = candidate
		}
	}
	if current != nil {
		response.CurrentStep = &dto.WorkflowRunStepResponse{
			StepID: current.NodeID, Type: current.Opcode, Status: current.Status,
			Attempt: current.AttemptCount, DeadlineAt: current.DeadlineAt,
			ErrorCode: safeErrorCode(current.ErrorCode), Error: safeOperationalJSON(current.ErrorJSON),
		}
	}
	if run.Status == runstore.RunStatusWaiting {
		waiting := &dto.WorkflowRunWaitingResponse{Kind: "step"}
		if current != nil {
			waiting.StepID = current.NodeID
			waiting.DeadlineAt = current.DeadlineAt
		}
		for index := range effects {
			effect := effects[index]
			if effect.Status == runstore.EffectStatusWaitingForResponder {
				// Keep the established broad waiting kind for clients while
				// exposing the capacity-specific machine reason as its subtype.
				waiting.Kind = "agent"
				waiting.Reason = runstore.WaitingReasonResponderCapacity
				waiting.DeadlineAt = effect.DeadlineAt
				break
			}
			if effect.Status != runstore.EffectStatusAwaiting &&
				effect.Status != runstore.EffectStatusPending &&
				effect.Status != runstore.EffectStatusDispatching {
				continue
			}
			switch effect.Kind {
			case runstore.EffectKindApproval:
				waiting.Kind = "approval"
			case runstore.EffectKindMessage:
				if effect.Status == runstore.EffectStatusAwaiting {
					waiting.Kind = "agent"
				} else {
					waiting.Kind = "delivery"
				}
			default:
				waiting.Kind = "external"
			}
			waiting.DeadlineAt = effect.DeadlineAt
			break
		}
		response.WaitingOn = waiting
	}
	return response
}

func RunLogResponse(value runstore.Event) dto.WorkflowRunLogEntryResponse {
	// The event-kind discriminator is an identity: validate the ORIGINAL
	// persisted bytes byte-for-byte against the closed producer kind vocabulary
	// before it fills any top-level field. A padded, invalid-UTF-8, token-bearing,
	// or unknown type fails closed to a neutral info-level generic entry; no
	// malformed byte reaches Kind, Level, or Message.
	kind, ok := eventcontract.ValidEventKind(value.Type)
	if !ok {
		return dto.WorkflowRunLogEntryResponse{
			Sequence: value.Seq, Time: value.CreatedAt, Level: "info",
			Kind:    "unknown",
			Message: "Workflow progressed",
			Details: safeOperationalEventJSON(value.PayloadJSON, value.Type),
		}
	}
	level := "info"
	if strings.Contains(kind, "failed") {
		level = "error"
	} else if strings.Contains(kind, "canceled") {
		level = "warn"
	}
	return dto.WorkflowRunLogEntryResponse{
		Sequence: value.Seq, Time: value.CreatedAt, Level: level,
		Kind: kind, Message: workflowRunLogMessage(kind),
		Details: safeOperationalEventJSON(value.PayloadJSON, kind),
	}
}

func workflowRunLogMessage(kind string) string {
	switch kind {
	case runstore.EventTypeEffectWaitingForResponderCapacity:
		return "Waiting for responder capacity"
	case runstore.EventTypeEffectResponderSelected:
		return "Responder selected"
	case runstore.EventTypeEffectResponderPromoted:
		return "Responder capacity promoted"
	}
	words := strings.ReplaceAll(strings.TrimSpace(kind), "_", " ")
	if words == "" {
		return "Workflow progressed"
	}
	return strings.ToUpper(words[:1]) + words[1:]
}

// RunLogMessage exposes the canonical human-readable event message to the
// Agent/CLI transport. Keep event wording here so those transports do not
// independently reinterpret lifecycle semantics.
func RunLogMessage(kind string) string { return workflowRunLogMessage(kind) }

func runResponse(value runstore.Run, includePayload bool) dto.WorkflowRunResponse {
	response := dto.WorkflowRunResponse{
		ID: value.ID, InvocationID: value.InvocationID, PlanID: value.PlanID,
		PlanKey: value.PlanKey, PlanDisplayName: value.PlanDisplayName,
		BindingID: value.BindingID, BindingKey: value.BindingKey,
		BindingDisplayName: value.BindingDisplayName,
		PlanVersionID:      value.PlanVersionID, Status: value.Status,
		ErrorCode: safeErrorCode(value.ErrorCode), QueuedAt: value.QueuedAt, StartedAt: value.StartedAt,
		CompletedAt: value.CompletedAt, CanceledAt: value.CanceledAt, Revision: value.Revision,
		CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt,
	}
	if includePayload {
		response.Input = cloneJSON(value.InputJSON)
		response.State = safeDurableJSON(value.StateJSON)
		response.Output = cloneJSON(value.OutputJSON)
		response.Error = safeOperationalJSON(value.ErrorJSON)
	}
	return response
}

func activationResponse(value runstore.Activation) dto.WorkflowRunActivationResponse {
	return dto.WorkflowRunActivationResponse{
		ID: value.ID, RunID: value.RunID, LogicalKey: value.LogicalKey,
		ParentActivationID: value.ParentActivationID, NodeID: value.NodeID,
		Opcode: value.Opcode, Status: value.Status, Input: cloneJSON(value.InputJSON),
		Output: cloneJSON(value.OutputJSON), ScopeContext: cloneJSON(value.ScopeContextJSON),
		SelectedPort: value.SelectedPort, AttemptCount: value.AttemptCount,
		RetryAt: value.RetryAt, WakeAt: value.WakeAt, DeadlineAt: value.DeadlineAt,
		ErrorCode: safeErrorCode(value.ErrorCode), Error: safeOperationalJSON(value.ErrorJSON),
		StartedAt: value.StartedAt, CompletedAt: value.CompletedAt,
		CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt,
	}
}

// ActivationResponse converts one Run activation for trusted transports.
func ActivationResponse(value runstore.Activation) dto.WorkflowRunActivationResponse {
	return activationResponse(value)
}

// EffectResponse converts one Run effect using the shared operational-data
// redaction policy.
func EffectResponse(value runstore.Effect) dto.WorkflowRunEffectResponse {
	return effectResponseWithQueue(value, runstore.EffectQueueContext{})
}

func effectResponseWithQueue(value runstore.Effect, queue runstore.EffectQueueContext) dto.WorkflowRunEffectResponse {
	response := dto.WorkflowRunEffectResponse{
		ID: value.ID, RunID: value.RunID, ActivationID: value.ActivationID,
		Kind: value.Kind, Status: value.Status, Request: safeOperationalJSON(value.RequestJSON),
		Result:       safeEffectResultJSON(value.ResultJSON),
		AttemptCount: value.AttemptCount, RetryAt: value.RetryAt, DeadlineAt: value.DeadlineAt,
		ErrorCode: safeErrorCode(value.ErrorCode), Error: safeOperationalJSON(value.ErrorJSON),
		DeliveredAt: value.DeliveredAt, CompletedAt: value.CompletedAt,
		QueuedAt:  value.QueuedAt,
		CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt,
	}
	response.CallbackResponderAgentID = strings.TrimSpace(queue.CallbackResponderAgentID)
	response.QueueStatus = strings.TrimSpace(queue.QueueStatus)
	if response.QueueStatus == "queued" {
		response.AheadCount = queue.AheadCount
		response.QueuePosition = queue.AheadCount + 1
	}
	response.RecoveryAttemptCount = value.RecoveryAttemptCount
	response.RecoveryPausedAt = value.RecoveryPausedAt
	response.ParkState = strings.TrimSpace(value.ParkState)
	if value.Status == runstore.EffectStatusWaitingForResponder {
		response.WaitingReason = runstore.WaitingReasonResponderCapacity
	}
	// Output the current execution-lease owner whenever one exists, not only when
	// this effect holds the lease, so a queued effect can show who is ahead.
	if strings.TrimSpace(queue.OwnerEffectID) != "" {
		response.OwnerRunID = strings.TrimSpace(queue.OwnerRunID)
		response.OwnerNodeID = strings.TrimSpace(queue.OwnerNodeID)
		response.OwnerEffectID = strings.TrimSpace(queue.OwnerEffectID)
		response.IsLeaseOwner = queue.HasLease
	}
	return response
}

func eventResponse(value runstore.Event) dto.WorkflowRunEventResponse {
	// The event-kind discriminator is an identity: validate the ORIGINAL
	// persisted bytes byte-for-byte so no malformed byte reaches the top-level
	// Type field. An unknown/malformed kind fails closed to the neutral string;
	// the payload still projects through the closed per-kind schema (which also
	// fails closed on the original bytes).
	kind, _ := eventcontract.ValidEventKind(value.Type)
	return dto.WorkflowRunEventResponse{
		ID: value.ID, RunID: value.RunID, Sequence: value.Seq, Type: kind,
		Payload: safeOperationalEventJSON(value.PayloadJSON, value.Type), CreatedAt: value.CreatedAt,
	}
}

// EventResponse converts one Run event using the shared operational-data
// redaction policy.
func EventResponse(value runstore.Event) dto.WorkflowRunEventResponse {
	return eventResponse(value)
}
