package fanout

import (
	"context"
	"encoding/json"
	"strings"

	"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"
)

type fakeMessageStore struct {
	sendTypeMessage            message.AppMessage
	sendTypeDelivery           message.MessageDelivery
	sendTypeErr                error
	lastMessageType            string
	lastClientID               string
	lastExpectedAuthorMemberID string
	lastContent                string
	lastInterruptPolicy        string
	lastMetadata               map[string]any
	lastMentionHints           []message.MessageMentionHint
	lastSource                 message.ContributionSource
	validateContents           []string
	messageValidateErr         error
}

func newFakeMessageStore() *fakeMessageStore {
	return &fakeMessageStore{
		sendTypeMessage: message.AppMessage{
			ID:        "msg_1",
			ChannelID: "chn_1",
			Content:   "hello",
		},
	}
}

func (fake *fakeMessageStore) SendMessageForUserWithType(_ context.Context, _ identity.User, _ string, _ string, content string, _ []string, messageType string) (message.AppMessage, message.MessageDelivery, error) {
	fake.lastMessageType = messageType
	fake.lastContent = content
	if fake.sendTypeErr != nil {
		return message.AppMessage{}, message.MessageDelivery{}, fake.sendTypeErr
	}
	return fake.sendTypeMessage, fake.sendTypeDelivery, nil
}

func (fake *fakeMessageStore) SendMessageForUserWithOptions(_ context.Context, _ identity.User, _ string, _ string, content string, _ []string, options message.HumanMessageCreateOptions) (message.AppMessage, message.MessageDelivery, error) {
	fake.lastMessageType = options.MessageType
	fake.lastClientID = options.ClientMessageID
	fake.lastExpectedAuthorMemberID = options.ExpectedAuthorMemberID
	fake.lastContent = content
	fake.lastInterruptPolicy = options.InterruptPolicy
	fake.lastMetadata = options.Metadata
	fake.lastMentionHints = options.MentionHints
	fake.lastSource = options.Source.Normalize()
	if fake.sendTypeErr != nil {
		return message.AppMessage{}, message.MessageDelivery{}, fake.sendTypeErr
	}
	appMessage := fake.sendTypeMessage
	appMessage.ClientMessageID = options.ClientMessageID
	if !options.Source.IsZero() {
		appMessage.Source = options.Source.Normalize()
	}
	delivery := fake.sendTypeDelivery
	if delivery.AppMessage != nil && !options.Source.IsZero() {
		deliveryAppMessage := *delivery.AppMessage
		deliveryAppMessage.Source = options.Source.Normalize()
		delivery.AppMessage = &deliveryAppMessage
	}
	return appMessage, delivery, nil
}

func (fake *fakeMessageStore) SendHumanMessage(ctx context.Context, user identity.User, workspaceID string, channelID string, content string, attachmentIDs []string, options messageservice.SendOptions) (message.AppMessage, message.MessageDelivery, error) {
	if options.Metadata != nil || options.ClientMessageID != "" || options.ExpectedAuthorMemberID != "" || options.AllowWorkflowContextMetadata || options.RequireSingleWorkflowCallbackResponder || options.WorkflowCallbackResponderAgentRef != "" || len(options.MentionHints) > 0 || options.StartAgentThread || !options.Source.IsZero() || options.InterruptPolicy != "" {
		return fake.SendMessageForUserWithOptions(ctx, user, workspaceID, channelID, content, attachmentIDs, message.HumanMessageCreateOptions{
			MessageType:                            options.MessageType,
			Metadata:                               options.Metadata,
			AllowWorkflowContextMetadata:           options.AllowWorkflowContextMetadata,
			RequireSingleWorkflowCallbackResponder: options.RequireSingleWorkflowCallbackResponder,
			WorkflowCallbackResponderAgentRef:      options.WorkflowCallbackResponderAgentRef,
			Source:                                 options.Source,
			MentionHints:                           options.MentionHints,
			ClientMessageID:                        options.ClientMessageID,
			ExpectedAuthorMemberID:                 options.ExpectedAuthorMemberID,
			StartAgentThread:                       options.StartAgentThread,
			InterruptPolicy:                        options.InterruptPolicy,
		})
	}
	return fake.SendMessageForUserWithType(ctx, user, workspaceID, channelID, content, attachmentIDs, options.MessageType)
}

func (fake *fakeMessageStore) ValidateHumanMessage(_ context.Context, _ identity.User, _ string, _ string, content string, _ messageservice.SendOptions) error {
	fake.validateContents = append(fake.validateContents, content)
	return fake.messageValidateErr
}

type fakeHub struct {
	notified       []string
	notifySeqs     []int64
	notifyMessages []message.ReceiveMessage
	notifyErr      error
	restoreErr     error
	restoreCalls   []runtimecontrol.RestoreAgentRecoveryPointRequest
	restoreMachine []string
	interruptCalls []string
}

func (fake *fakeHub) NotifyAgentMessage(_ context.Context, recipient message.AgentRecipient, seq int64, msg message.ReceiveMessage) error {
	fake.notified = append(fake.notified, recipient.AgentID)
	fake.notifySeqs = append(fake.notifySeqs, seq)
	fake.notifyMessages = append(fake.notifyMessages, msg)
	return fake.notifyErr
}

func (fake *fakeHub) RestoreAgentRecoveryPoint(_ context.Context, machineID string, request runtimecontrol.RestoreAgentRecoveryPointRequest) error {
	fake.restoreMachine = append(fake.restoreMachine, machineID)
	fake.restoreCalls = append(fake.restoreCalls, request)
	return fake.restoreErr
}

func (fake *fakeHub) InterruptAgentRun(machineID string, agentID string) error {
	fake.interruptCalls = append(fake.interruptCalls, strings.TrimSpace(machineID)+"/"+strings.TrimSpace(agentID))
	return nil
}

type fakeRealtime struct {
	messageNew                             []string
	messageNewMessages                     []message.AppMessage
	messageNewSuppressBrowserNotifications []bool
	deliveryUpdated                        []string
	reactionChanged                        []string
	reactionNotify                         int
	mentions                               int
	conversationRewind                     []string
	unreadUpdated                          []unread.UnreadDelta
	threadUpdated                          []string
	seenEventIDs                           map[string]struct{}
	appendErr                              error
}

func (fake *fakeRealtime) EmitUnreadUpdated(_ string, delta unread.UnreadDelta) {
	fake.unreadUpdated = append(fake.unreadUpdated, delta)
}

func (fake *fakeRealtime) AppendScopeEvent(_ context.Context, request im.ScopeEventRequest) ([]im.StreamEvent, error) {
	if fake.appendErr != nil {
		return nil, fake.appendErr
	}
	if request.Event.EventID != "" {
		if fake.seenEventIDs == nil {
			fake.seenEventIDs = map[string]struct{}{}
		}
		if _, exists := fake.seenEventIDs[request.Event.EventID]; exists {
			return nil, nil
		}
		fake.seenEventIDs[request.Event.EventID] = struct{}{}
	}
	scopeID := request.ScopeID
	if scopeID == "" {
		scopeID = request.Event.ScopeID
	}
	switch request.Event.Type {
	case imEventTypeMessageCreated:
		fake.messageNew = append(fake.messageNew, scopeID)
		suppressBrowserNotification, _ := request.Event.Payload["suppressBrowserNotification"].(bool)
		fake.messageNewSuppressBrowserNotifications = append(fake.messageNewSuppressBrowserNotifications, suppressBrowserNotification)
		if _, ok := request.Event.Payload["mention"]; ok {
			fake.mentions++
		}
		if raw := request.Event.Payload["message"]; raw != nil {
			encoded, _ := json.Marshal(raw)
			var appMessage message.AppMessage
			_ = json.Unmarshal(encoded, &appMessage)
			fake.messageNewMessages = append(fake.messageNewMessages, appMessage)
		}
	case imEventTypeReactionChanged:
		fake.reactionChanged = append(fake.reactionChanged, scopeID+":"+request.Event.EntityID)
		if _, ok := request.Event.Payload["reaction"]; ok {
			fake.reactionNotify++
		}
	case imEventTypeThreadUpdated:
		fake.threadUpdated = append(fake.threadUpdated, scopeID+":"+request.Event.EntityID)
	case imEventTypeConversationRewound:
		channelID, _ := request.Event.Payload["channelId"].(string)
		fake.conversationRewind = append(fake.conversationRewind, scopeID+":"+channelID)
	case imEventTypeMessageDelivery:
		fake.deliveryUpdated = append(fake.deliveryUpdated, scopeID+":"+request.Event.EntityID)
	}
	return []im.StreamEvent{{WorkspaceID: request.WorkspaceID, ScopeID: scopeID, Type: request.Event.Type, EntityID: request.Event.EntityID, Payload: request.Event.Payload}}, nil
}
