package approvalapp

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/runeffect"
	"github.com/tidbcloud/AgentTeam/internal/service/appruntime"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/workflow/orchestrator"
)

func (handler *Handler) handleWorkflowRunAction(
	ctx context.Context,
	envelope appruntime.InternalHandlerEnvelope,
	payload actionEnvelope,
) (appruntime.InternalHandlerOutcome, error) {
	if handler == nil || handler.workflowSink == nil || handler.projector == nil || handler.projector.notifications == nil || handler.projector.provisioner == nil {
		return appruntime.InternalHandlerOutcome{}, fmt.Errorf("Approval App Workflow Run handler is not configured")
	}
	if payload.Notification.BusinessObject.Version != 1 || payload.Notification.MessageID == "" || payload.Actor.MemberID == "" {
		return appruntime.InternalHandlerOutcome{}, fmt.Errorf("Workflow Run approval action actor or version is invalid")
	}
	if payload.Action.ActionKey != ActionApprove && payload.Action.ActionKey != ActionReject {
		return appruntime.InternalHandlerOutcome{}, fmt.Errorf("Workflow Run approval action is not supported")
	}
	comment, commentProvided, err := workflowRunApprovalComment(payload.Action.Input)
	if err != nil {
		return appruntime.InternalHandlerOutcome{}, err
	}
	decision := orchestrator.ApprovalDecisionApproved
	if payload.Action.ActionKey == ActionReject {
		decision = orchestrator.ApprovalDecisionRejected
	}
	nowFn := handler.now
	if nowFn == nil {
		nowFn = time.Now
	}
	now := nowFn().UTC()
	command := orchestrator.ApprovalDecision{
		WorkspaceID:      strings.TrimSpace(envelope.Delivery.WorkspaceID),
		EffectID:         payload.Notification.BusinessObject.ID,
		NotificationID:   payload.Notification.ID,
		ActorMemberID:    payload.Actor.MemberID,
		Decision:         decision,
		ClientDecisionID: payload.Action.InvocationID,
		Comment:          comment, CommentProvided: commentProvided,
		DecidedAt: now,
	}
	if err := command.Validate(); err != nil {
		return appruntime.InternalHandlerOutcome{}, err
	}
	result, err := handler.decideWorkflowRunApproval(ctx, command, payload.Actor.UserID, comment)
	if err != nil {
		// A terminal Run/effect with an already-resolved receipt is a safe
		// convergence path. A different decision for an already-completed
		// effect remains a conflict and must reach the delivery retry/error
		// policy instead of being silently presented as an idempotent replay.
		if errors.Is(err, orchestrator.ErrApprovalNotPending) {
			return handler.settledWorkflowRunActionOutcome(ctx, envelope, payload, decision, comment, err)
		}
		return appruntime.InternalHandlerOutcome{}, err
	}
	if err := validateWorkflowRunDecisionResult(result, envelope.Delivery.WorkspaceID, payload.Notification.BusinessObject.ID, payload.Actor.MemberID, decision); err != nil {
		return appruntime.InternalHandlerOutcome{}, err
	}
	if strings.TrimSpace(result.Comment) != strings.TrimSpace(comment) {
		return appruntime.InternalHandlerOutcome{}, fmt.Errorf("Approval App Workflow Run sink substituted committed comment")
	}
	if commentProvided && result.FeedbackScope == nil {
		return appruntime.InternalHandlerOutcome{}, fmt.Errorf("Workflow Run approval comment has no frozen feedback scope")
	}
	if err := handler.sendWorkflowRunFeedback(ctx, envelope.Delivery.WorkspaceID, payload.Actor.UserID, payload.Actor.MemberID, result); err != nil {
		return appruntime.InternalHandlerOutcome{}, err
	}
	if result.Replayed {
		return handler.resolveReplayedWorkflowRunApprovalCard(ctx, envelope, payload, result)
	}
	return handler.resolveWorkflowRunApprovalCard(ctx, envelope, payload, result)
}

func (handler *Handler) decideWorkflowRunApproval(
	ctx context.Context,
	command orchestrator.ApprovalDecision,
	actorUserID, comment string,
) (orchestrator.ApprovalDecisionResult, error) {
	if strings.TrimSpace(comment) == "" {
		return handler.workflowSink.DecideApproval(ctx, command)
	}
	atomicSink, ok := handler.workflowSink.(orchestrator.PreCommitMessageDecisionSink)
	if !ok {
		return orchestrator.ApprovalDecisionResult{}, fmt.Errorf("Workflow Run approval comment requires an atomic decision sink")
	}
	writer, ok := handler.projector.notifications.(workflowRunFeedbackMessageAtomicWriter)
	if !ok {
		return orchestrator.ApprovalDecisionResult{}, fmt.Errorf("Workflow Run approval atomic message writer is not configured")
	}
	mutation := workflowRunApprovalFeedbackPreCommitMutation(
		writer, actorUserID, command.ActorMemberID, command.WorkspaceID,
		command.EffectID, command.Decision, comment,
	)
	return atomicSink.DecideApprovalWithPreCommitWrite(ctx, command, mutation)
}

func (handler *Handler) resolveWorkflowRunApprovalCard(
	ctx context.Context,
	envelope appruntime.InternalHandlerEnvelope,
	payload actionEnvelope,
	result orchestrator.ApprovalDecisionResult,
) (appruntime.InternalHandlerOutcome, error) {
	processorName, err := handler.projector.provisioner.MemberDisplayName(ctx, result.WorkspaceID, result.DecidedByMemberID)
	if err != nil {
		return appruntime.InternalHandlerOutcome{}, err
	}
	content, err := resolvedApprovalCardContent(result, processorName)
	if err != nil {
		return appruntime.InternalHandlerOutcome{}, err
	}
	businessVersion := int64(2)
	correlationID := strings.TrimSpace(payload.CorrelationID)
	if correlationID == "" {
		correlationID = result.RunID
	}
	resolved, err := handler.projector.notifications.MutateAppNotificationForBuiltinDelivery(
		ctx, IdentityID, envelope.Delivery.WorkspaceID, envelope.Delivery.ID, envelope.Attempt.ID,
		app.AppNotificationMutationCommand{
			NotificationID:   payload.Notification.ID,
			Kind:             app.AppNotificationMutationResolve,
			ExpectedRevision: payload.Notification.Revision,
			ClientMutationID: "workflow-approval-decision:" + payload.Action.InvocationID,
			Content:          &content, BusinessObjectVersion: &businessVersion,
			CausedByActor: app.AppNotificationActorRef{Type: "human", ID: payload.Actor.MemberID},
			CorrelationID: correlationID,
		},
	)
	if err != nil {
		return appruntime.InternalHandlerOutcome{}, err
	}
	if strings.TrimSpace(resolved.NotificationID) != payload.Notification.ID ||
		strings.TrimSpace(resolved.MessageID) != payload.Notification.MessageID ||
		resolved.NotificationRevision <= payload.Notification.Revision ||
		strings.TrimSpace(resolved.Status) != app.AppNotificationStatusResolved {
		return appruntime.InternalHandlerOutcome{}, fmt.Errorf("Approval App Workflow Run notification mutation returned an inconsistent receipt")
	}
	resultJSON, err := json.Marshal(map[string]any{
		"effectId": result.EffectID, "runId": result.RunID, "decision": result.Decision,
		"decidedByMemberId": result.DecidedByMemberID, "decidedAt": result.DecidedAt.UTC(),
		"decisionReplayed": result.Replayed, "notificationId": resolved.NotificationID,
		"notificationRevision": resolved.NotificationRevision,
	})
	if err != nil {
		return appruntime.InternalHandlerOutcome{}, err
	}
	return appruntime.InternalHandlerOutcome{
		Effect:     runeffect.NotificationResolvedEffect(resolved.NotificationID, resolved.MessageID),
		ResultJSON: resultJSON,
	}, nil
}

func (handler *Handler) settledWorkflowRunActionOutcome(
	ctx context.Context,
	envelope appruntime.InternalHandlerEnvelope,
	payload actionEnvelope,
	decision, comment string,
	decisionErr error,
) (appruntime.InternalHandlerOutcome, error) {
	current, err := handler.projector.notifications.GetAppNotificationForBuiltin(
		ctx, IdentityID, envelope.Delivery.WorkspaceID, payload.Notification.ID,
	)
	if err != nil {
		return appruntime.InternalHandlerOutcome{}, err
	}
	if current.Status == app.AppNotificationStatusResolved {
		if strings.TrimSpace(current.NotificationID) == payload.Notification.ID &&
			strings.TrimSpace(current.MessageID) == payload.Notification.MessageID &&
			current.NotificationRevision > payload.Notification.Revision {
			return settledWorkflowRunResolvedOutcome(payload, current, decisionErr)
		}
		return appruntime.InternalHandlerOutcome{}, decisionErr
	}
	resolver, ok := handler.workflowSink.(orchestrator.ApprovalDecisionReceiptResolver)
	if !ok {
		return appruntime.InternalHandlerOutcome{}, decisionErr
	}
	result, err := resolver.LoadCompletedApproval(
		ctx, envelope.Delivery.WorkspaceID, payload.Notification.BusinessObject.ID,
		payload.Notification.ID, payload.Actor.MemberID,
	)
	if err != nil {
		return appruntime.InternalHandlerOutcome{}, decisionErr
	}
	if err := validateWorkflowRunDecisionResult(
		result, envelope.Delivery.WorkspaceID, payload.Notification.BusinessObject.ID,
		payload.Actor.MemberID, decision,
	); err != nil || strings.TrimSpace(result.Comment) != strings.TrimSpace(comment) {
		return appruntime.InternalHandlerOutcome{}, decisionErr
	}
	// The effect/message receipt is already committed. Only retry the pending
	// card projection; never call sendWorkflowRunFeedback or re-authorize the
	// ordinary Human message on this path. Use the latest card revision so a
	// failed first mutation can converge through the same idempotent mutation.
	payload.Notification.Revision = current.NotificationRevision
	payload.Notification.MessageID = current.MessageID
	return handler.resolveWorkflowRunApprovalCard(ctx, envelope, payload, result)
}

func (handler *Handler) resolveReplayedWorkflowRunApprovalCard(
	ctx context.Context,
	envelope appruntime.InternalHandlerEnvelope,
	payload actionEnvelope,
	result orchestrator.ApprovalDecisionResult,
) (appruntime.InternalHandlerOutcome, error) {
	current, err := handler.projector.notifications.GetAppNotificationForBuiltin(
		ctx, IdentityID, envelope.Delivery.WorkspaceID, payload.Notification.ID,
	)
	if err != nil {
		return appruntime.InternalHandlerOutcome{}, err
	}
	if current.Status == app.AppNotificationStatusResolved {
		return settledWorkflowRunResolvedOutcome(payload, current, nil)
	}
	if strings.TrimSpace(current.NotificationID) != payload.Notification.ID ||
		strings.TrimSpace(current.MessageID) != payload.Notification.MessageID {
		return appruntime.InternalHandlerOutcome{}, fmt.Errorf("Workflow Run approval replay notification identity changed")
	}
	// A prior attempt may have committed the decision and message before its
	// card mutation lost an optimistic revision race. Reuse the latest card
	// revision while retaining the immutable effect/message receipt.
	payload.Notification.Revision = current.NotificationRevision
	payload.Notification.MessageID = current.MessageID
	return handler.resolveWorkflowRunApprovalCard(ctx, envelope, payload, result)
}

func settledWorkflowRunResolvedOutcome(
	payload actionEnvelope,
	current app.AppNotificationResult,
	decisionErr error,
) (appruntime.InternalHandlerOutcome, error) {
	reason := "not_pending"
	if errors.Is(decisionErr, orchestrator.ErrApprovalDecisionConflict) {
		reason = "decision_conflict"
	}
	resultJSON, err := json.Marshal(map[string]any{
		"effectId": payload.Notification.BusinessObject.ID, "accepted": false,
		"settled": true, "reason": reason, "notificationId": current.NotificationID,
		"notificationRevision": current.NotificationRevision,
	})
	if err != nil {
		return appruntime.InternalHandlerOutcome{}, err
	}
	return appruntime.InternalHandlerOutcome{
		Effect:     runeffect.NotificationResolvedEffect(current.NotificationID, current.MessageID),
		ResultJSON: resultJSON,
	}, nil
}

func validateWorkflowRunDecisionResult(result orchestrator.ApprovalDecisionResult, workspaceID, effectID, actorMemberID, decision string) error {
	if err := result.Validate(); err != nil {
		return fmt.Errorf("Approval App Workflow Run sink returned an invalid result: %w", err)
	}
	if strings.TrimSpace(result.WorkspaceID) != strings.TrimSpace(workspaceID) ||
		strings.TrimSpace(result.EffectID) != strings.TrimSpace(effectID) ||
		strings.TrimSpace(result.DecidedByMemberID) != strings.TrimSpace(actorMemberID) ||
		strings.TrimSpace(result.AssigneeMemberID) != strings.TrimSpace(actorMemberID) ||
		strings.TrimSpace(result.Decision) != strings.TrimSpace(decision) {
		return fmt.Errorf("Approval App Workflow Run sink substituted decision identity")
	}
	return nil
}
