package fanout

import (
	"context"
	"fmt"
	"sort"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/workflow/orchestrator"
)

type WorkflowRunMessageReceiptStore struct{ db coredb.DB }

func NewWorkflowRunMessageReceiptStore(db coredb.DB) WorkflowRunMessageReceiptStore {
	return WorkflowRunMessageReceiptStore{db: db}
}

func (store WorkflowRunMessageReceiptStore) LoadWorkflowRunMessageReceipt(ctx context.Context, request orchestrator.MessageRequest, messageID string) (orchestrator.MessageResult, error) {
	if err := request.Validate(); err != nil {
		return orchestrator.MessageResult{}, fmt.Errorf("validate workflow Run message receipt request: %w", err)
	}
	workspaceID, messageID := strings.TrimSpace(request.WorkspaceID), strings.TrimSpace(messageID)
	if store.db == nil || workspaceID == "" || messageID == "" {
		return orchestrator.MessageResult{}, fmt.Errorf("workflow Run message receipt store and identity are required")
	}
	var raw []byte
	err := store.db.QueryRow(ctx, `
SELECT payload
FROM scope_fanout_outbox
WHERE workspace_id=$1 AND message_id=$2
  AND event_ref_type=$3 AND fanout_type=$4
LIMIT 1`, workspaceID, messageID, message.DeliverySourceRefMessage, message.ScopeOutboxFanoutScope).Scan(&raw)
	if err != nil {
		return orchestrator.MessageResult{}, fmt.Errorf("load workflow Run message fanout receipt: %w", err)
	}
	payload, err := message.UnmarshalScopeFanoutPayload(raw)
	if err != nil {
		return orchestrator.MessageResult{}, fmt.Errorf("decode workflow Run message fanout receipt: %w", err)
	}
	if strings.TrimSpace(payload.AppMessage.ID) != messageID || strings.TrimSpace(payload.Delivery.MessageID) != messageID {
		return orchestrator.MessageResult{}, fmt.Errorf("workflow Run message fanout receipt identity mismatch")
	}
	if err := validateWorkflowRunMessageReceiptIdentity(request, payload); err != nil {
		return orchestrator.MessageResult{}, err
	}
	seen := make(map[string]struct{}, len(payload.Delivery.WakeRecipients))
	agents := make([]string, 0, len(payload.Delivery.WakeRecipients))
	for _, recipient := range payload.Delivery.WakeRecipients {
		agentID := strings.TrimSpace(recipient.AgentID)
		if agentID == "" {
			continue
		}
		if _, duplicate := seen[agentID]; duplicate {
			continue
		}
		seen[agentID] = struct{}{}
		agents = append(agents, agentID)
	}
	sort.Strings(agents)
	deliveryScope, err := workflowRunMessageReceiptDeliveryScope(payload.AppMessage)
	if err != nil {
		return orchestrator.MessageResult{}, err
	}
	callbackResponderAgentID, err := message.WorkflowCallbackResponderAgentID(payload.AppMessage.Metadata)
	if err != nil {
		return orchestrator.MessageResult{}, fmt.Errorf("decode workflow Run callback responder receipt: %w", err)
	}
	threadScope, err := workflowRunMessageReceiptThreadScope(messageID, payload.Delivery)
	if err != nil {
		return orchestrator.MessageResult{}, err
	}
	// The frozen minimal member context was persisted atomically with the
	// message in the same outbox payload. Recovery returns it verbatim so
	// callback/retry/replay reuse the exact first-frozen snapshot and never
	// re-read mutable Team/member/role state.
	frozenMemberContext, err := workflowFrozenMemberContext(payload.AppMessage, nil)
	if err != nil {
		return orchestrator.MessageResult{}, fmt.Errorf("decode workflow Run Team member context receipt: %w", err)
	}
	return orchestrator.MessageResult{
		MessageID: messageID, WakeAgentIDs: agents, CallbackResponderAgentID: callbackResponderAgentID,
		DeliveryScope: &deliveryScope, ThreadScope: threadScope, IdempotentReplay: true,
		TeamMemberContext: frozenMemberContext,
	}, nil
}

func validateWorkflowRunMessageReceiptIdentity(request orchestrator.MessageRequest, payload message.ScopeFanoutPayload) error {
	wantClientMessageID := strings.TrimSpace(request.ClientMessageID)
	if strings.TrimSpace(payload.AppMessage.ClientMessageID) != wantClientMessageID ||
		strings.TrimSpace(payload.Delivery.ClientMessageID) != wantClientMessageID {
		return fmt.Errorf("workflow Run message fanout receipt client message identity mismatch")
	}
	// Author identity is a fail-closed authorization fence: the recovered
	// receipt must belong to the exact frozen Human who authorized this Run. The
	// visible AuthorProfile of a workflow message is the canonical Workflow
	// service author, so the durable Human authorization/audit principal is
	// carried separately on the delivery notify principal. Validate that
	// principal (legacy payloads fall back to the then-visible profile member).
	durableAuthorMemberID := strings.TrimSpace(payload.Delivery.NotifyPrincipalMemberID)
	if durableAuthorMemberID == "" {
		durableAuthorMemberID = strings.TrimSpace(payload.Delivery.Message.NotifyPrincipalMemberID)
	}
	if durableAuthorMemberID == "" {
		durableAuthorMemberID = strings.TrimSpace(payload.AppMessage.AuthorProfile.MemberID)
	}
	if durableAuthorMemberID != strings.TrimSpace(request.AuthorMemberID) {
		return fmt.Errorf("workflow Run message fanout receipt author identity mismatch")
	}
	source := payload.AppMessage.Source.Normalize()
	if source.Kind != message.ContributionSourceKindWorkflowRun ||
		source.RefType != message.ContributionSourceRefTypeWorkflowRun ||
		source.RefID != strings.TrimSpace(request.RunID) ||
		source.RunID != strings.TrimSpace(request.RunID) ||
		source.EventID != strings.TrimSpace(request.EffectID) {
		return fmt.Errorf("workflow Run message fanout receipt contribution source mismatch")
	}
	metadata := payload.AppMessage.Metadata
	for _, expected := range []struct {
		key   string
		value string
	}{
		{message.WorkflowContextMetadataWorkspaceIDKey, request.WorkspaceID},
		{message.WorkflowContextMetadataRunIDKey, request.RunID},
		{message.WorkflowContextMetadataEffectIDKey, request.EffectID},
		{message.WorkflowContextMetadataNodeIDKey, request.NodeID},
	} {
		actual, ok := metadata[expected.key].(string)
		if !ok || actual != strings.TrimSpace(expected.value) {
			return fmt.Errorf("workflow Run message fanout receipt metadata %s mismatch", expected.key)
		}
	}
	callbackExpected, callbackPresent := metadata[message.WorkflowContextMetadataCallbackExpectedKey]
	if request.Callback {
		value, ok := callbackExpected.(bool)
		if !callbackPresent || !ok || !value {
			return fmt.Errorf("workflow Run message fanout receipt callback identity mismatch")
		}
	} else if callbackPresent {
		return fmt.Errorf("workflow Run message fanout receipt callback identity mismatch")
	}
	return nil
}

func workflowRunMessageReceiptThreadScope(messageID string, delivery message.MessageDelivery) (*domain.MessageScopeValue, error) {
	if delivery.StartedReplyThread == nil {
		return nil, nil
	}
	if strings.TrimSpace(delivery.StartedReplyThread.RootMessageID) != strings.TrimSpace(messageID) {
		return nil, fmt.Errorf("workflow Run message fanout receipt has a mismatched reply thread root")
	}
	scope, err := domain.NormalizeMessageScopeValue(domain.MessageScopeValue{
		Kind:    domain.MessageScopeKindThread,
		ScopeID: strings.TrimSpace(delivery.StartedReplyThread.ThreadChannelID),
	})
	if err != nil {
		return nil, fmt.Errorf("workflow Run message fanout receipt has an invalid reply thread scope: %w", err)
	}
	return &scope, nil
}

func workflowRunMessageReceiptDeliveryScope(appMessage message.AppMessage) (domain.MessageScopeValue, error) {
	if rawScope, present := appMessage.Metadata[message.WorkflowContextMetadataDeliveryScopeKey]; present {
		deliveryScope, err := domain.NormalizeMessageScopeValue(rawScope)
		if err != nil {
			return domain.MessageScopeValue{}, fmt.Errorf("decode workflow Run message delivery scope: %w", err)
		}
		return deliveryScope, nil
	}

	// Messages committed before workflow_delivery_scope was introduced still
	// carry their canonical scope identity in the same immutable fanout payload.
	// Recover only from those persisted fields: never reinterpret the authored
	// target or consult mutable current topology. The writer compares this value
	// with the idempotency reservation's resolved destination before accepting
	// the replay.
	deliveryScope, err := domain.NormalizeMessageScopeValue(domain.MessageScopeValue{
		Kind:    domain.MessageScopeKind(appMessage.ChannelType),
		ScopeID: appMessage.ChannelID,
	})
	if err != nil {
		return domain.MessageScopeValue{}, fmt.Errorf("decode legacy workflow Run message delivery scope: %w", err)
	}
	return deliveryScope, nil
}

var _ WorkflowRunMessageReceiptResolver = WorkflowRunMessageReceiptStore{}
