package fanout

import (
	"context"
	"log/slog"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/runtimecontrol"
	messageservice "github.com/tidbcloud/AgentTeam/internal/service/message"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/im"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/unread"
)

// HumanMessageWriter is the narrow post-commit human-message creation workflow.
type HumanMessageWriter interface {
	ValidateHumanMessage(ctx context.Context, user identity.User, workspaceID string, channelID string, content string, options messageservice.SendOptions) error
	SendHumanMessage(ctx context.Context, user identity.User, workspaceID string, channelID string, content string, attachmentIDs []string, options messageservice.SendOptions) (message.AppMessage, message.MessageDelivery, error)
}

// AgentNotifier forwards persisted deliveries to connected daemon machines.
type AgentNotifier interface {
	NotifyAgentMessage(ctx context.Context, recipient message.AgentRecipient, seq int64, message message.ReceiveMessage) error
}

// ReplyThreadRootPreloader decorates live wake payloads with a visible thread
// root preview before they are forwarded to daemon agents.
type ReplyThreadRootPreloader interface {
	PreloadReplyThreadRootForRecipient(ctx context.Context, workspaceID string, recipient message.AgentRecipient, msg message.ReceiveMessage) (message.ReceiveMessage, error)
}

// ScopeFanoutDeliveryMarker marks message fan-out outbox rows that were
// successfully delivered by the synchronous request path.
type ScopeFanoutDeliveryMarker interface {
	MarkScopeFanoutDeliveredForMessage(ctx context.Context, messageID string) error
}

// ScopeFanoutLeaseCoordinator is the fenced Task delivery boundary shared by
// the synchronous request path and the durable outbox worker. Implementations
// give exactly one dispatcher a live outbox lease and persist per-Agent
// receipts so a partial retry only attempts recipients that have not accepted
// this immutable message/activity event.
//
// It is discovered as an optional extension of ScopeFanoutDeliveryMarker so
// older test adapters and rolling binaries retain their existing contract.
type ScopeFanoutLeaseCoordinator interface {
	ClaimScopeFanoutForMessage(ctx context.Context, messageID string, leaseOwner string, leaseUntil time.Time) (message.ScopeFanoutOutboxRecord, bool, error)
	RenewScopeFanoutLease(ctx context.Context, outboxID string, leaseOwner string, leaseUntil time.Time) error
	ScopeFanoutAgentDelivered(ctx context.Context, sourceEventType string, sourceEventID string, agentID string) (bool, error)
	RecordScopeFanoutAgentDelivered(ctx context.Context, outboxID string, leaseOwner string, sourceEventType string, sourceEventID string, agentID string) error
	CompleteScopeFanoutLease(ctx context.Context, outboxID string, leaseOwner string) error
	RetryScopeFanoutLease(ctx context.Context, outboxID string, leaseOwner string, nextAttemptAt time.Time, lastError string) error
}

// ScopeFanoutLeaseDeferrer is the optional v2 lease operation used when a
// durable delivery is waiting for owner-local prerequisite state rather than
// failing. Keeping it separate from ScopeFanoutLeaseCoordinator preserves the
// rolling/test adapter contract while allowing new stores to release the lease
// without consuming the delivery's real failure budget.
type ScopeFanoutLeaseDeferrer interface {
	DeferScopeFanoutLease(ctx context.Context, outboxID string, leaseOwner string, nextAttemptAt time.Time, lastError string) error
}

// TaskScopeLifecycleGuard is the final post-commit guard for Task delivery
// fan-out. Durable outbox payloads may already be leased when a Task is
// deleted. The guard holds a lifecycle row lock for the whole callback, so
// either every side effect happens before the delete commits or the callback
// is skipped after observing the irreversible tombstone.
type TaskScopeLifecycleGuard interface {
	WithActiveTaskScopes(ctx context.Context, workspaceID string, scopeIDs []string, fanout func() error) (bool, error)
}

// RewindHub restores agent runtime recovery points before applying rewound tombstones.
type RewindHub interface {
	AgentNotifier
	RestoreAgentRecoveryPoint(ctx context.Context, machineID string, request runtimecontrol.RestoreAgentRecoveryPointRequest) error
	InterruptAgentRun(machineID string, agentID string) error
}

// StreamEventWriter owns Workspace IM stream fanout for browser-visible message mutations.
type StreamEventWriter interface {
	AppendScopeEvent(ctx context.Context, request im.ScopeEventRequest) ([]im.StreamEvent, error)
}

type ScopeLiveEventPublisher interface {
	PublishScopeLiveOnlyEvent(workspaceID string, scopeID string, event im.StreamEvent)
}

// UnreadRuntime receives post-commit unread events for owner-local memory.
type UnreadRuntime interface {
	ApplyMessage(event unread.MessageStored) (unread.ApplyResult, error)
	ApplyNonMessageActivity(event unread.NonMessageActivityStored) (unread.ApplyResult, error)
	ApplyMessageRewound(event unread.MessageRewound) (unread.ApplyResult, error)
	ApplyScopeInvalidated(event unread.ScopeInvalidated) error
	ApplyScopeInvalidatedWithDeltas(event unread.ScopeInvalidated) ([]unread.UnreadDelta, error)
}

// TaskUnreadSnapshotBarrier persists the exact frozen Human recipients'
// owner-local Task unread state before a v2 outbox lease may wake Agents or
// become delivered.
type TaskUnreadSnapshotBarrier interface {
	PersistTaskUnreadSnapshots(ctx context.Context, workspaceID string, scopeID string, memberIDs []string) error
}

// UnreadDeltaSink emits owner-local unread deltas after synchronous write-path
// runtime updates.
type UnreadDeltaSink interface {
	EmitUnreadUpdated(workspaceID string, delta unread.UnreadDelta)
}

// Dependencies contains the shared message fanout dependencies that are reused
// by schedule effects and legacy route adapters while the old routes are being
// removed.
type Dependencies struct {
	HumanMessages            HumanMessageWriter
	Hub                      AgentNotifier
	StreamWriter             StreamEventWriter
	ReplyThreadRootPreloader ReplyThreadRootPreloader
	ScopeFanoutMarker        ScopeFanoutDeliveryMarker
	TaskScopeLifecycle       TaskScopeLifecycleGuard
	UnreadRuntime            UnreadRuntime
	UnreadDeltaSink          UnreadDeltaSink
	Logger                   *slog.Logger
}
