package message

import (
	"context"
	"fmt"
	"log/slog"
	"strings"

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

// SessionAuthenticator resolves session tokens to authenticated users.
type SessionAuthenticator interface {
	AuthenticateSession(ctx context.Context, token string) (identity.AuthenticatedUser, error)
}

// WorkspaceResolver resolves workspace membership for a user.
type WorkspaceResolver interface {
	GetWorkspaceForUser(ctx context.Context, userID string, workspaceID string) (workspace.Summary, error)
}

type CreationStore interface {
	ValidateAndAuditMessageForUser(ctx context.Context, user identity.User, workspaceID string, channelID string, content string, options message.HumanMessageCreateOptions) error
	SendMessageForUserWithOptions(ctx context.Context, user identity.User, workspaceID string, channelID string, content string, attachmentIDs []string, options message.HumanMessageCreateOptions) (message.AppMessage, message.MessageDelivery, error)
	EditMessageForUser(ctx context.Context, user identity.User, workspaceID string, channelID string, messageID string, content string, options message.HumanMessageEditOptions) (message.AppMessage, error)
	SendMessageForUserWithType(ctx context.Context, user identity.User, workspaceID string, channelID string, content string, attachmentIDs []string, messageType string) (message.AppMessage, message.MessageDelivery, error)
	SendMessageForUserWithMetadata(ctx context.Context, user identity.User, workspaceID string, targetRef string, content string, attachmentIDs []string, metadata map[string]any) (message.AppMessage, message.MessageDelivery, error)
}

type QueryStore interface {
	ListMessagesAroundForUser(ctx context.Context, userID string, workspaceID string, channelID string, aroundMessageID string, limit int) (message.AppMessagePage, error)
	ListMessagesForUser(ctx context.Context, userID string, workspaceID string, channelID string, limit int, before *int64, after *int64) (message.AppMessagePage, error)
	GetMessageDeliveryDiagnosticsForUser(ctx context.Context, userID string, workspaceID string, messageID string) (message.MessageDeliveryDiagnostics, error)
}

type SearchStore interface {
	SupportsMessageSearch(ctx context.Context) bool
	SearchMessagesForUser(ctx context.Context, userID string, workspaceID string, query string, channelID string, options message.MessageSearchOptions) (message.MessageSearchResult, error)
}

type appMessageReadDecorator interface {
	DecorateAppMessageForUser(ctx context.Context, userID string, workspaceID string, appMessage message.AppMessage) (message.AppMessage, error)
}

type appMessageRecaller interface {
	RecallMessageForUser(ctx context.Context, user identity.User, workspaceID string, channelID string, messageID string, options message.HumanMessageRecallOptions) (message.AppMessageRecallResult, error)
}

type Dependencies struct {
	Sessions   SessionAuthenticator
	Workspaces WorkspaceResolver
	Creation   CreationStore
	Queries    QueryStore
	Search     SearchStore
	Logger     *slog.Logger
}

type Service struct {
	sessions   SessionAuthenticator
	workspaces WorkspaceResolver
	creation   CreationStore
	queries    QueryStore
	search     SearchStore
	logger     *slog.Logger
}

func New(deps Dependencies) *Service {
	return &Service{
		sessions:   deps.Sessions,
		workspaces: deps.Workspaces,
		creation:   deps.Creation,
		queries:    deps.Queries,
		search:     deps.Search,
		logger:     deps.Logger,
	}
}

func (service *Service) AuthenticateSession(ctx context.Context, token string) (identity.AuthenticatedUser, error) {
	if service == nil || service.sessions == nil {
		return identity.AuthenticatedUser{}, fmt.Errorf("message auth service is not configured")
	}
	return service.sessions.AuthenticateSession(ctx, token)
}

func (service *Service) GetWorkspaceForUser(ctx context.Context, userID string, workspaceID string) (workspace.Summary, error) {
	if service == nil || service.workspaces == nil {
		return workspace.Summary{}, fmt.Errorf("message workspace service is not configured")
	}
	return service.workspaces.GetWorkspaceForUser(ctx, userID, workspaceID)
}

// SendOptions describes the message creation mode. Ordinary typed messages and
// metadata-only facade messages are separate modes. Human-visible system
// notices may carry metadata so clients can localize the stored notice key, and
// service-account posts carry trusted card metadata that is validated by the
// target-aware service-account writer.
type SendOptions struct {
	MessageType string
	Metadata    map[string]any
	// AllowWorkflowContextMetadata is an internal-only trust bit. HTTP/public
	// adapters must leave it false; workflow effect writers opt in explicitly.
	AllowWorkflowContextMetadata bool
	// RequireSingleWorkflowCallbackResponder is an internal-only routing bit.
	// It is accepted only together with trusted callback workflow metadata.
	RequireSingleWorkflowCallbackResponder bool
	// WorkflowCallbackResponderAgentRef is the compiler-selected AgentRef for a
	// first-class callback. The normal message transaction resolves it to one
	// visible canonical Agent and materializes an ordinary canonical mention.
	WorkflowCallbackResponderAgentRef string
	// WorkflowRunTeamID is the frozen server-authorized Team of a Run with a
	// frozen Team ID (Team-linked binding or dynamic-explicit teamId). When set
	// with a callback responder, the message transaction verifies the responder
	// is an active Team member before persisting or fanning out any message
	// (zero side effects on rejection).
	WorkflowRunTeamID string
	// WorkflowCallbackExpectedResponderAgentID is an internal-only trusted fence
	// set after the callback responder is frozen. The message transaction rolls
	// back with zero visible side effects if the resolved responder differs.
	WorkflowCallbackExpectedResponderAgentID string
	WorkflowCallbackTaskWatchHandoff         *message.WorkflowCallbackTaskWatchHandoff
	Source                                   message.ContributionSource
	MentionHints                             []message.MessageMentionHint
	StartAgentThread                         bool
	ClientMessageID                          string
	// ExpectedAuthorMemberID is a trusted internal identity fence for delayed
	// Human-authored writes. When set, creation fails unless the owner's current
	// workspace member is still exactly this member.
	ExpectedAuthorMemberID     string
	InterruptPolicy            string
	OwnerQuoteSourceMessageIDs []string
}

// EditOptions describes human ordinary text message edit knobs.
type EditOptions struct {
	MentionHints []message.MessageMentionHint
}

func validateSendOptions(options SendOptions) error {
	messageType := strings.TrimSpace(options.MessageType)
	if err := message.ValidateWorkflowCallbackResponderRouting(options.Metadata, options.AllowWorkflowContextMetadata, options.RequireSingleWorkflowCallbackResponder); err != nil {
		return err
	}
	if err := message.ValidateWorkflowCallbackResponderAgentRef(
		options.Metadata,
		options.RequireSingleWorkflowCallbackResponder,
		options.WorkflowCallbackResponderAgentRef,
	); err != nil {
		return err
	}
	if err := message.ValidateWorkflowContextMetadata(options.Metadata, options.AllowWorkflowContextMetadata); err != nil {
		return err
	}
	if _, err := message.NormalizeWorkflowCallbackTaskWatchHandoff(
		options.WorkflowCallbackTaskWatchHandoff,
		options.AllowWorkflowContextMetadata,
		options.RequireSingleWorkflowCallbackResponder,
		options.WorkflowCallbackResponderAgentRef,
	); err != nil {
		return err
	}
	if options.RequireSingleWorkflowCallbackResponder && options.StartAgentThread {
		return fmt.Errorf("single workflow callback responder routing does not support starting an agent thread")
	}
	if messageType != "" && options.Metadata != nil && messageType != message.MessageTypeSystem && messageType != message.MessageTypeServiceAccountPost {
		return fmt.Errorf("messageType and metadata cannot both be set")
	}
	if options.StartAgentThread && messageType != "" {
		return fmt.Errorf("agent thread routing does not support messageType")
	}
	if _, ok := message.NormalizeAgentInterruptPolicy(options.InterruptPolicy); !ok {
		return fmt.Errorf("unsupported interruptPolicy")
	}
	return nil
}

func (service *Service) ValidateHumanMessage(ctx context.Context, owner identity.User, workspaceID string, targetRef string, content string, options SendOptions) error {
	if service == nil || service.creation == nil {
		return fmt.Errorf("message creation service is not configured")
	}
	if err := validateSendOptions(options); err != nil {
		return err
	}
	return service.creation.ValidateAndAuditMessageForUser(ctx, owner, workspaceID, targetRef, content, message.HumanMessageCreateOptions{
		MessageType:                              options.MessageType,
		Metadata:                                 options.Metadata,
		AllowWorkflowContextMetadata:             options.AllowWorkflowContextMetadata,
		RequireSingleWorkflowCallbackResponder:   options.RequireSingleWorkflowCallbackResponder,
		WorkflowCallbackResponderAgentRef:        strings.TrimSpace(options.WorkflowCallbackResponderAgentRef),
		WorkflowRunTeamID:                        strings.TrimSpace(options.WorkflowRunTeamID),
		WorkflowCallbackExpectedResponderAgentID: strings.TrimSpace(options.WorkflowCallbackExpectedResponderAgentID),
		WorkflowCallbackTaskWatchHandoff:         options.WorkflowCallbackTaskWatchHandoff,
		Source:                                   options.Source,
		MentionHints:                             options.MentionHints,
		ClientMessageID:                          options.ClientMessageID,
		ExpectedAuthorMemberID:                   options.ExpectedAuthorMemberID,
		StartAgentThread:                         options.StartAgentThread,
		InterruptPolicy:                          strings.TrimSpace(options.InterruptPolicy),
		OwnerQuoteSourceMessageIDs:               options.OwnerQuoteSourceMessageIDs,
	})
}

func (service *Service) SendHumanMessage(ctx context.Context, owner identity.User, workspaceID string, targetRef string, content string, attachmentIDs []string, options SendOptions) (message.AppMessage, message.MessageDelivery, error) {
	if service == nil || service.creation == nil {
		return message.AppMessage{}, message.MessageDelivery{}, fmt.Errorf("message creation service is not configured")
	}
	if err := validateSendOptions(options); err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	if strings.TrimSpace(options.ClientMessageID) != "" || strings.TrimSpace(options.ExpectedAuthorMemberID) != "" || len(options.MentionHints) > 0 || options.StartAgentThread || !options.Source.IsZero() || strings.TrimSpace(options.InterruptPolicy) != "" || len(options.OwnerQuoteSourceMessageIDs) > 0 || options.AllowWorkflowContextMetadata || options.RequireSingleWorkflowCallbackResponder || strings.TrimSpace(options.WorkflowCallbackResponderAgentRef) != "" || options.WorkflowCallbackTaskWatchHandoff != nil || (strings.TrimSpace(options.MessageType) != "" && options.Metadata != nil) {
		return service.creation.SendMessageForUserWithOptions(ctx, owner, workspaceID, targetRef, content, attachmentIDs, message.HumanMessageCreateOptions{
			MessageType:                              options.MessageType,
			Metadata:                                 options.Metadata,
			AllowWorkflowContextMetadata:             options.AllowWorkflowContextMetadata,
			RequireSingleWorkflowCallbackResponder:   options.RequireSingleWorkflowCallbackResponder,
			WorkflowCallbackResponderAgentRef:        strings.TrimSpace(options.WorkflowCallbackResponderAgentRef),
			WorkflowRunTeamID:                        strings.TrimSpace(options.WorkflowRunTeamID),
			WorkflowCallbackExpectedResponderAgentID: strings.TrimSpace(options.WorkflowCallbackExpectedResponderAgentID),
			WorkflowCallbackTaskWatchHandoff:         options.WorkflowCallbackTaskWatchHandoff,
			Source:                                   options.Source,
			MentionHints:                             options.MentionHints,
			ClientMessageID:                          options.ClientMessageID,
			ExpectedAuthorMemberID:                   options.ExpectedAuthorMemberID,
			StartAgentThread:                         options.StartAgentThread,
			InterruptPolicy:                          strings.TrimSpace(options.InterruptPolicy),
			OwnerQuoteSourceMessageIDs:               options.OwnerQuoteSourceMessageIDs,
		})
	}
	if strings.TrimSpace(options.MessageType) != "" {
		return service.creation.SendMessageForUserWithType(ctx, owner, workspaceID, targetRef, content, attachmentIDs, options.MessageType)
	}
	if options.Metadata != nil {
		return service.creation.SendMessageForUserWithMetadata(ctx, owner, workspaceID, targetRef, content, attachmentIDs, options.Metadata)
	}
	return service.creation.SendMessageForUserWithType(ctx, owner, workspaceID, targetRef, content, attachmentIDs, "")
}

func (service *Service) EditHumanMessage(ctx context.Context, owner identity.User, workspaceID string, targetRef string, messageID string, content string, options EditOptions) (message.AppMessage, error) {
	if service == nil || service.creation == nil {
		return message.AppMessage{}, fmt.Errorf("message edit service is not configured")
	}
	return service.creation.EditMessageForUser(ctx, owner, workspaceID, targetRef, messageID, content, message.HumanMessageEditOptions{
		MentionHints: options.MentionHints,
	})
}

func (service *Service) RecallHumanMessage(ctx context.Context, owner identity.User, workspaceID string, targetRef string, messageID string) (message.AppMessageRecallResult, error) {
	if service == nil || service.creation == nil {
		return message.AppMessageRecallResult{}, fmt.Errorf("message recall service is not configured")
	}
	recaller, ok := service.creation.(appMessageRecaller)
	if !ok {
		return message.AppMessageRecallResult{}, fmt.Errorf("message recall service is not configured")
	}
	return recaller.RecallMessageForUser(ctx, owner, workspaceID, targetRef, messageID, message.HumanMessageRecallOptions{})
}

func (service *Service) DecorateAppMessageForUser(ctx context.Context, userID string, workspaceID string, appMessage message.AppMessage) (message.AppMessage, error) {
	if service == nil || service.queries == nil {
		return appMessage, nil
	}
	decorator, ok := service.queries.(appMessageReadDecorator)
	if !ok {
		return appMessage, nil
	}
	decorated, err := decorator.DecorateAppMessageForUser(ctx, userID, workspaceID, appMessage)
	if err != nil {
		if service.logger != nil {
			service.logger.Warn("decorate app message for user", slog.Any("error", err))
		}
		return appMessage, nil
	}
	return decorated, nil
}

func (service *Service) SupportsMessageSearch(ctx context.Context) bool {
	return service != nil && service.search != nil && service.search.SupportsMessageSearch(ctx)
}

func (service *Service) ListMessagesAroundForUser(ctx context.Context, userID string, workspaceID string, channelID string, aroundMessageID string, limit int) (message.AppMessagePage, error) {
	if service == nil || service.queries == nil {
		return message.AppMessagePage{}, fmt.Errorf("message query service is not configured")
	}
	return service.queries.ListMessagesAroundForUser(ctx, userID, workspaceID, channelID, aroundMessageID, limit)
}

func (service *Service) ListMessagesForUser(ctx context.Context, userID string, workspaceID string, channelID string, limit int, before *int64, after *int64) (message.AppMessagePage, error) {
	if service == nil || service.queries == nil {
		return message.AppMessagePage{}, fmt.Errorf("message query service is not configured")
	}
	return service.queries.ListMessagesForUser(ctx, userID, workspaceID, channelID, limit, before, after)
}

func (service *Service) GetMessageDeliveryDiagnosticsForUser(ctx context.Context, userID string, workspaceID string, messageID string) (message.MessageDeliveryDiagnostics, error) {
	if service == nil || service.queries == nil {
		return message.MessageDeliveryDiagnostics{}, fmt.Errorf("message query service is not configured")
	}
	return service.queries.GetMessageDeliveryDiagnosticsForUser(ctx, userID, workspaceID, messageID)
}

func (service *Service) SearchMessagesForUser(ctx context.Context, userID string, workspaceID string, query string, channelID string, options message.MessageSearchOptions) (message.MessageSearchResult, error) {
	if service == nil || service.search == nil {
		return message.MessageSearchResult{}, fmt.Errorf("message search service is not configured")
	}
	return service.search.SearchMessagesForUser(ctx, userID, workspaceID, query, channelID, options)
}
