package message

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
)

type fakeCreationStore struct {
	lastMessageType                       string
	lastClientID                          string
	lastExpectedAuthorMemberID            string
	lastRequireWorkflowCallbackResponder  bool
	lastWorkflowCallbackResponderAgentRef string
	lastMetadata                          map[string]any
	lastMentionHints                      []message.MessageMentionHint
	lastEditMentionHints                  []message.MessageMentionHint
	lastStartAgentThread                  bool
	lastOwnerQuoteSourceMessageIDs        []string
	validateContent                       string
	validateMessageType                   string
	validateErr                           error
	sendCalls                             int
	validateCalls                         int
}

type fakeQueryStore struct{}

func (fake *fakeCreationStore) SendMessageForUserWithOptions(_ context.Context, _ identity.User, _ string, _ string, _ string, _ []string, options message.HumanMessageCreateOptions) (message.AppMessage, message.MessageDelivery, error) {
	fake.sendCalls++
	fake.lastMessageType = options.MessageType
	fake.lastMetadata = options.Metadata
	fake.lastMentionHints = options.MentionHints
	fake.lastClientID = options.ClientMessageID
	fake.lastExpectedAuthorMemberID = options.ExpectedAuthorMemberID
	fake.lastRequireWorkflowCallbackResponder = options.RequireSingleWorkflowCallbackResponder
	fake.lastWorkflowCallbackResponderAgentRef = options.WorkflowCallbackResponderAgentRef
	fake.lastStartAgentThread = options.StartAgentThread
	fake.lastOwnerQuoteSourceMessageIDs = options.OwnerQuoteSourceMessageIDs
	return message.AppMessage{ID: "msg_options"}, message.MessageDelivery{MessageID: "msg_options"}, nil
}

func (fake *fakeCreationStore) SendMessageForUserWithType(_ context.Context, _ identity.User, _ string, _ string, _ string, _ []string, messageType string) (message.AppMessage, message.MessageDelivery, error) {
	fake.sendCalls++
	fake.lastMessageType = messageType
	return message.AppMessage{ID: "msg_typed"}, message.MessageDelivery{MessageID: "msg_typed"}, nil
}

func (fake *fakeCreationStore) SendMessageForUserWithMetadata(_ context.Context, _ identity.User, _ string, _ string, _ string, _ []string, metadata map[string]any) (message.AppMessage, message.MessageDelivery, error) {
	fake.sendCalls++
	fake.lastMetadata = metadata
	return message.AppMessage{ID: "msg_meta"}, message.MessageDelivery{MessageID: "msg_meta"}, nil
}

func (fake *fakeCreationStore) EditMessageForUser(_ context.Context, _ identity.User, _ string, _ string, messageID string, content string, options message.HumanMessageEditOptions) (message.AppMessage, error) {
	fake.validateContent = content
	fake.lastEditMentionHints = options.MentionHints
	return message.AppMessage{ID: messageID, Content: content}, nil
}

func (fake *fakeCreationStore) ValidateAndAuditMessageForUser(_ context.Context, _ identity.User, _ string, _ string, content string, options message.HumanMessageCreateOptions) error {
	fake.validateCalls++
	fake.validateContent = content
	fake.validateMessageType = options.MessageType
	return fake.validateErr
}

func TestHumanMessageServiceRejectsReservedWorkflowMetadataBeforeCreationStore(t *testing.T) {
	t.Parallel()

	creation := &fakeCreationStore{}
	service := New(Dependencies{Creation: creation})
	owner := identity.User{ID: "usr_1", Name: "alice"}
	metadata := map[string]any{message.WorkflowContextMetadataRunIDKey: "wri_forged"}

	_, _, err := service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "forged", nil, SendOptions{Metadata: metadata})
	require.ErrorContains(t, err, message.WorkflowContextMetadataRunIDKey)
	require.Zero(t, creation.sendCalls)

	err = service.ValidateHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "forged", SendOptions{Metadata: metadata})
	require.ErrorContains(t, err, message.WorkflowContextMetadataRunIDKey)
	require.Zero(t, creation.validateCalls)
}

func TestHumanMessageServiceFencesSingleWorkflowCallbackResponderMode(t *testing.T) {
	t.Parallel()

	creation := &fakeCreationStore{}
	service := New(Dependencies{Creation: creation})
	owner := identity.User{ID: "usr_1", Name: "alice"}
	callbackMetadata := message.BuildWorkflowContextMetadata(message.WorkflowContextMetadataParams{
		WorkspaceID: "wsp_1", PlanID: "pln_1", PlanVersionID: "plv_1",
		RunID: "wri_1", EffectID: "wrf_1", NodeID: "ask", SourceKind: "manual",
		CallbackExpected: true,
	})

	_, _, err := service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "@agent answer", nil, SendOptions{
		Metadata: callbackMetadata, RequireSingleWorkflowCallbackResponder: true,
	})
	require.ErrorContains(t, err, "requires trusted workflow context metadata")
	require.Zero(t, creation.sendCalls)

	_, _, err = service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "@agent answer", nil, SendOptions{
		Metadata:                     map[string]any{message.WorkflowContextMetadataRunIDKey: "wri_1"},
		AllowWorkflowContextMetadata: true, RequireSingleWorkflowCallbackResponder: true,
	})
	require.ErrorContains(t, err, "requires callbackExpected workflow metadata")
	require.Zero(t, creation.sendCalls)

	_, _, err = service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "@agent answer", nil, SendOptions{
		Metadata: callbackMetadata, AllowWorkflowContextMetadata: true,
		RequireSingleWorkflowCallbackResponder: true,
	})
	require.NoError(t, err)
	require.True(t, creation.lastRequireWorkflowCallbackResponder)
}

func TestHumanMessageServicePassesTrustedSelectedWorkflowCallbackResponder(t *testing.T) {
	t.Parallel()

	creation := &fakeCreationStore{}
	service := New(Dependencies{Creation: creation})
	owner := identity.User{ID: "usr_1", Name: "alice"}
	metadata := message.BuildWorkflowContextMetadata(message.WorkflowContextMetadataParams{
		WorkspaceID: "wsp_1", RunID: "wri_1", EffectID: "wrf_1",
		CallbackExpected: true, ResponderAgentRef: "agt_exact",
	})
	_, _, err := service.SendHumanMessage(
		context.Background(),
		owner,
		"wsp_1",
		"chn_1",
		"Review this result.",
		nil,
		SendOptions{
			Metadata:                               metadata,
			AllowWorkflowContextMetadata:           true,
			RequireSingleWorkflowCallbackResponder: true,
			WorkflowCallbackResponderAgentRef:      "agt_exact",
		},
	)
	require.NoError(t, err)
	require.Equal(t, "agt_exact", creation.lastWorkflowCallbackResponderAgentRef)

	_, _, err = service.SendHumanMessage(
		context.Background(),
		owner,
		"wsp_1",
		"chn_1",
		"mismatched",
		nil,
		SendOptions{
			Metadata:                               metadata,
			AllowWorkflowContextMetadata:           true,
			RequireSingleWorkflowCallbackResponder: true,
			WorkflowCallbackResponderAgentRef:      "agt_other",
		},
	)
	require.ErrorContains(t, err, "does not match workflow metadata")
	require.Equal(t, 1, creation.sendCalls)
}

func (fake *fakeQueryStore) ListMessagesAroundForUser(context.Context, string, string, string, string, int) (message.AppMessagePage, error) {
	return message.AppMessagePage{}, nil
}

func (fake *fakeQueryStore) ListMessagesForUser(context.Context, string, string, string, int, *int64, *int64) (message.AppMessagePage, error) {
	return message.AppMessagePage{}, nil
}

func (fake *fakeQueryStore) GetMessageDeliveryDiagnosticsForUser(context.Context, string, string, string) (message.MessageDeliveryDiagnostics, error) {
	return message.MessageDeliveryDiagnostics{}, nil
}

func TestSendHumanMessageUsesFocusedCreationModes(t *testing.T) {
	t.Parallel()

	creation := &fakeCreationStore{}
	service := New(Dependencies{Creation: creation})
	owner := identity.User{ID: "usr_1", Name: "alice"}

	_, delivery, err := service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "hello", nil, SendOptions{MessageType: "system"})
	require.NoError(t, err)
	require.Equal(t, "msg_typed", delivery.MessageID)
	require.Equal(t, "system", creation.lastMessageType)

	metadata := map[string]any{"kind": "trigger"}
	_, delivery, err = service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "hello", nil, SendOptions{Metadata: metadata})
	require.NoError(t, err)
	require.Equal(t, "msg_meta", delivery.MessageID)
	require.Equal(t, metadata, creation.lastMetadata)

	_, delivery, err = service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "hello", nil, SendOptions{StartAgentThread: true})
	require.NoError(t, err)
	require.Equal(t, "msg_options", delivery.MessageID)
	require.True(t, creation.lastStartAgentThread)
}

func TestSendHumanMessagePassesClientMessageIDToCreationMode(t *testing.T) {
	t.Parallel()

	creation := &fakeCreationStore{}
	service := New(Dependencies{Creation: creation})
	owner := identity.User{ID: "usr_1", Name: "alice"}

	metadata := map[string]any{"systemNotice": map[string]any{"kind": "rewind.conversation"}}
	_, delivery, err := service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "hello", nil, SendOptions{MessageType: "system", Metadata: metadata, ClientMessageID: "client-1"})

	require.NoError(t, err)
	require.Equal(t, "msg_options", delivery.MessageID)
	require.Equal(t, "system", creation.lastMessageType)
	require.Equal(t, metadata, creation.lastMetadata)
	require.Equal(t, "client-1", creation.lastClientID)
}

func TestSendHumanMessagePassesExpectedAuthorMemberIDToTransactionalCreationMode(t *testing.T) {
	t.Parallel()

	creation := &fakeCreationStore{}
	service := New(Dependencies{Creation: creation})
	owner := identity.User{ID: "usr_1", Name: "alice"}

	_, delivery, err := service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "hello", nil, SendOptions{
		ExpectedAuthorMemberID: "mem_frozen",
	})

	require.NoError(t, err)
	require.Equal(t, "msg_options", delivery.MessageID)
	require.Equal(t, "mem_frozen", creation.lastExpectedAuthorMemberID)
}

func TestSendHumanMessagePassesClientMessageIDToAgentThreadCreation(t *testing.T) {
	t.Parallel()

	creation := &fakeCreationStore{}
	service := New(Dependencies{Creation: creation})
	owner := identity.User{ID: "usr_1", Name: "alice"}

	_, delivery, err := service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "hello", nil, SendOptions{StartAgentThread: true, ClientMessageID: "client-thread-1"})

	require.NoError(t, err)
	require.Equal(t, "msg_options", delivery.MessageID)
	require.Equal(t, "client-thread-1", creation.lastClientID)
	require.True(t, creation.lastStartAgentThread)
}

func TestSendHumanMessagePassesMentionHintsToCreationMode(t *testing.T) {
	t.Parallel()

	creation := &fakeCreationStore{}
	service := New(Dependencies{Creation: creation})
	owner := identity.User{ID: "usr_1", Name: "alice"}
	hints := []message.MessageMentionHint{
		{Type: "agent", ID: "agt_1", Token: "helper"},
	}

	_, delivery, err := service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "hello @helper", nil, SendOptions{MentionHints: hints})

	require.NoError(t, err)
	require.Equal(t, "msg_options", delivery.MessageID)
	require.Equal(t, hints, creation.lastMentionHints)
}

func TestEditHumanMessagePassesMentionHintsToCreationMode(t *testing.T) {
	t.Parallel()

	creation := &fakeCreationStore{}
	service := New(Dependencies{Creation: creation})
	owner := identity.User{ID: "usr_1", Name: "alice"}
	hints := []message.MessageMentionHint{
		{Type: "agent", ID: "agt_1", Token: "helper"},
	}

	edited, err := service.EditHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "msg_1", "edited @helper", EditOptions{MentionHints: hints})

	require.NoError(t, err)
	require.Equal(t, "msg_1", edited.ID)
	require.Equal(t, "edited @helper", edited.Content)
	require.Equal(t, hints, creation.lastEditMentionHints)
}

func TestSendHumanMessagePassesOwnerQuoteSourceMessageIDsToCreationOptions(t *testing.T) {
	t.Parallel()

	creation := &fakeCreationStore{}
	service := New(Dependencies{Creation: creation})
	owner := identity.User{ID: "usr_1", Name: "alice"}

	_, delivery, err := service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "owner quote", nil, SendOptions{
		MessageType:                "owner_quote",
		OwnerQuoteSourceMessageIDs: []string{"msg-source"},
	})

	require.NoError(t, err)
	require.Equal(t, "msg_options", delivery.MessageID)
	require.Equal(t, "owner_quote", creation.lastMessageType)
	require.Equal(t, []string{"msg-source"}, creation.lastOwnerQuoteSourceMessageIDs)
}

func TestValidateHumanMessagePassesMessageOptionsToCreationStore(t *testing.T) {
	t.Parallel()

	creation := &fakeCreationStore{}
	service := New(Dependencies{Creation: creation})
	owner := identity.User{ID: "usr_1", Name: "alice"}

	err := service.ValidateHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "hello", SendOptions{MessageType: "owner_quote"})

	require.NoError(t, err)
	require.Equal(t, "hello", creation.validateContent)
	require.Equal(t, "owner_quote", creation.validateMessageType)
}

func TestSendHumanMessageRejectsFacadeLikeMixedModes(t *testing.T) {
	t.Parallel()

	service := New(Dependencies{Creation: &fakeCreationStore{}})
	owner := identity.User{ID: "usr_1", Name: "alice"}

	_, _, err := service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "hello", nil, SendOptions{MessageType: "owner_quote", Metadata: map[string]any{"kind": "trigger"}})
	require.ErrorContains(t, err, "messageType and metadata cannot both be set")

	_, _, err = service.SendHumanMessage(context.Background(), owner, "wsp_1", "chn_1", "hello", nil, SendOptions{StartAgentThread: true, MessageType: "system"})
	require.ErrorContains(t, err, "agent thread routing does not support messageType")
}

func TestSendHumanMessageAllowsServiceAccountPostCardMetadata(t *testing.T) {
	t.Parallel()

	creation := &fakeCreationStore{}
	service := New(Dependencies{Creation: creation})
	owner := identity.User{ID: "usr_1", Name: "alice"}
	metadata := map[string]any{"serviceAccountPost": map[string]any{"title": "Release 2026.7"}}

	_, delivery, err := service.SendHumanMessage(context.Background(), owner, "wsp_1", "sca_1", "release notes", nil, SendOptions{
		MessageType: message.MessageTypeServiceAccountPost,
		Metadata:    metadata,
	})

	require.NoError(t, err)
	require.Equal(t, "msg_options", delivery.MessageID)
	require.Equal(t, message.MessageTypeServiceAccountPost, creation.lastMessageType)
	require.Equal(t, metadata, creation.lastMetadata)
}
