package app

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/coordinator"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

const defaultReviewFeedbackListLimit = 100

// CreateReviewFeedbackForUser posts review feedback as a message in the review
// context's thread. Review feedback requires a thread-bound review context.
func (store *Store) CreateReviewFeedbackForUser(ctx context.Context, user model.User, workspaceID string, rc agent.ReviewContext, params message.ReviewFeedbackBuildRequest) (message.AppMessage, message.MessageDelivery, error) {
	if strings.TrimSpace(rc.ID) == "" {
		return message.AppMessage{}, message.MessageDelivery{}, badRequest("reviewContextId is required")
	}
	threadID := strings.TrimSpace(rc.ThreadID)
	if threadID == "" {
		return message.AppMessage{}, message.MessageDelivery{}, badRequest("review feedback requires a thread-bound review context")
	}
	params.Context = reviewFeedbackContextSnapshot(rc)
	if params.SubmittedAt.IsZero() && strings.TrimSpace(params.ClientMessageID) == "" {
		params.SubmittedAt = time.Now().UTC()
	}
	payload, err := message.BuildReviewFeedbackPayload(params)
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	metadata, err := message.BuildReviewFeedbackMetadata(payload)
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	content := message.FormatReviewFeedbackMessage(payload)
	sourceLabel := message.ReviewFeedbackContributionSourceLabel(payload)
	sourceAgentRecipients, err := store.reviewFeedbackSourceAgentRecipientsForUser(ctx, user.ID, workspaceID, rc)
	if err != nil {
		return message.AppMessage{}, message.MessageDelivery{}, err
	}
	return store.SendAppMessageForUserWithOptions(ctx, user, workspaceID, threadID, content, nil, SendAppMessageOptions{
		Metadata:            metadata,
		Source:              message.ContributionSourceForReviewComment("", sourceLabel),
		ClientMessageID:     strings.TrimSpace(params.ClientMessageID),
		ExplicitRecipients:  sourceAgentRecipients,
		ExplicitWakeTargets: sourceAgentRecipients,
	})
}

func (store *Store) reviewFeedbackSourceAgentRecipientsForUser(ctx context.Context, userID string, workspaceID string, rc agent.ReviewContext) ([]message.AgentRecipient, error) {
	sourceAgentID := strings.TrimSpace(rc.SourceAgentID)
	if sourceAgentID == "" {
		return nil, nil
	}
	recipient, err := store.ResolveChannelAgentRecipientByIDForUser(ctx, userID, workspaceID, strings.TrimSpace(rc.ThreadID), sourceAgentID)
	if err != nil {
		return nil, err
	}
	recipient.DeliveryReason = message.AgentDeliveryReasonDirectMention
	return []message.AgentRecipient{recipient}, nil
}

// ListReviewFeedbackForUser lists the review feedback messages for a review
// context within its thread.
func (store *Store) ListReviewFeedbackForUser(ctx context.Context, userID string, workspaceID string, rc agent.ReviewContext, limit int) ([]message.AppMessage, error) {
	if strings.TrimSpace(rc.ID) == "" {
		return nil, badRequest("reviewContextId is required")
	}
	if limit <= 0 || limit > defaultReviewFeedbackListLimit {
		limit = defaultReviewFeedbackListLimit
	}
	membership, target, err := store.resolveAuthorizedHumanMessageTarget(ctx, userID, workspaceID, strings.TrimSpace(rc.ThreadID), channelReadAction, dmReadAction, threadReadAction)
	if err != nil {
		return nil, err
	}
	scopeID := message.ScopeIDForThreadAccess(target)
	if scopeID == "" {
		return nil, badRequest("review feedback requires a thread-bound review context")
	}
	rows, err := store.db.Query(ctx, fmt.Sprintf(`
WITH page AS (
    SELECT id, seq, author_member_id, author_type, client_message_id, message_type, content, metadata, created_at,
           edited_at, edited_by_member_id, edit_revision
    FROM scope_messages
    WHERE workspace_id = $1
      AND scope_id = $2
      AND %s
      AND JSON_UNQUOTE(JSON_EXTRACT(CASE WHEN JSON_VALID(metadata) THEN metadata ELSE JSON_OBJECT() END, '$.reviewFeedback.reviewContextId')) = $3
    ORDER BY seq ASC
    LIMIT $4
)
SELECT
    page.id,
    page.seq,
    $2 AS channel_id,
    page.author_type,
    author_member.id,
    u.id,
    a.id,
    a.name,
    u.display_name,
    author_member.display_name,
    u.name,
    u.avatar_url,
    a.avatar_url,
    page.client_message_id,
    page.message_type,
    page.content,
    page.metadata,
    page.created_at,
    page.edited_at,
    COALESCE(page.edited_by_member_id, ''),
    page.edit_revision
FROM page
JOIN members author_member ON author_member.id = page.author_member_id
LEFT JOIN users u ON page.author_type = 'human' AND u.id = author_member.user_id
LEFT JOIN agents a ON page.author_type = 'agent' AND a.member_id = author_member.id
ORDER BY page.seq ASC
`, humanVisibleMessageTypeSQL("scope_messages")), strings.TrimSpace(workspaceID), scopeID, strings.TrimSpace(rc.ID), limit)
	if err != nil {
		return nil, fmt.Errorf("load review feedback messages: %w", err)
	}
	defer rows.Close()

	messages := make([]message.AppMessage, 0)
	for rows.Next() {
		appMessage, err := scanAppMessageRow(rows)
		if err != nil {
			return nil, err
		}
		messages = append(messages, appMessage)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate review feedback messages: %w", err)
	}
	attachmentLookup, err := store.loadAttachmentsForMessages(ctx, workspaceID, collectAppMessageIDs(messages))
	if err != nil {
		return nil, err
	}
	for i := range messages {
		messages[i].Attachments = attachmentLookup[messages[i].ID]
	}
	if err := store.attachAppMessageReadDecorations(ctx, workspaceID, membership.Member.ID, messages); err != nil {
		return nil, err
	}
	return messages, nil
}

// SetReviewFeedbackItemStatusForUser updates a single review-feedback item's
// status within the review context's thread.
func (store *Store) SetReviewFeedbackItemStatusForUser(ctx context.Context, user model.User, workspaceID string, rc agent.ReviewContext, messageID string, itemID string, status string) (message.AppMessage, error) {
	trimmedMessageID := strings.TrimSpace(messageID)
	if trimmedMessageID == "" {
		return message.AppMessage{}, badRequest("messageId is required")
	}
	if strings.TrimSpace(rc.ID) == "" {
		return message.AppMessage{}, badRequest("reviewContextId is required")
	}

	var appMessage message.AppMessage
	var viewerMemberID string
	if err := coordinator.New(store.db).Run(ctx, "set review feedback item status", func(tx coredb.Tx) error {
		membership, target, err := store.resolveAuthorizedHumanMessageTargetWithQueryer(ctx, tx, user.ID, workspaceID, strings.TrimSpace(rc.ThreadID), channelPostAction, dmPostAction, threadPostAction)
		if err != nil {
			return err
		}
		viewerMemberID = strings.TrimSpace(membership.Member.ID)
		scopeID := message.ScopeIDForThreadAccess(target)
		if scopeID == "" {
			return badRequest("review feedback requires a thread-bound review context")
		}
		repos := message.NewScopeRepositories(tx)
		record, err := repos.Messages.LoadByID(ctx, workspaceID, scopeID, trimmedMessageID)
		if err != nil {
			return err
		}
		readCheck, err := message.NewScopeAPIServiceForQueryer(tx).CanReadMessage(ctx, message.ScopeAPIReadCheckRequest{
			WorkspaceID:    workspaceID,
			MessageID:      trimmedMessageID,
			ViewerMemberID: viewerMemberID,
			ViewerType:     message.ScopeActorHuman,
			RequestKind:    message.ScopeVisibilityRequestHistory,
		})
		if err != nil {
			return err
		}
		if !readCheck.Allowed {
			return notFound("message not found")
		}
		if strings.TrimSpace(record.AuthorType) != "human" || strings.TrimSpace(record.AuthorMemberID) != viewerMemberID {
			return forbidden("only the review feedback author can update item status")
		}
		if strings.TrimSpace(record.MessageType) != "" {
			return badRequest("message is not review feedback")
		}
		metadata, err := message.DecodeMessageMetadata(record.Metadata)
		if err != nil {
			return err
		}
		payload, ok, err := message.ReviewFeedbackPayloadFromMetadata(metadata)
		if err != nil {
			return err
		}
		if !ok || strings.TrimSpace(payload.ReviewContextID) != strings.TrimSpace(rc.ID) {
			return badRequest("message is not review feedback for this review context")
		}
		updatedMetadata, _, err := message.UpdateReviewFeedbackItemStatus(metadata, itemID, status, viewerMemberID, time.Now().UTC())
		if err != nil {
			return err
		}
		encodedMetadata, normalizedMetadata, err := message.EncodeMessageMetadata(updatedMetadata)
		if err != nil {
			return err
		}
		updatedAt := time.Now().UTC()
		if _, err := tx.Exec(ctx, `
UPDATE scope_messages
SET metadata = $4,
    updated_at = $5
WHERE workspace_id = $1
  AND scope_id = $2
  AND id = $3
`, workspaceID, scopeID, trimmedMessageID, encodedMetadata, updatedAt); err != nil {
			return fmt.Errorf("update review feedback status metadata: %w", err)
		}
		updated, err := repos.Messages.LoadByID(ctx, workspaceID, scopeID, trimmedMessageID)
		if err != nil {
			return err
		}
		if err := repos.SyncDirty.MarkMessageDirtyForScope(ctx, workspaceID, scopeID, updated.Seq, "review_feedback_status", updatedAt); err != nil {
			return err
		}
		appMessage = appMessageFromEditedRecord(appChannelIDForTarget(target), target, humanAuthorProfileFromUser(user, viewerMemberID, membership.Member.DisplayName), updated, normalizedMetadata)
		return nil
	}); err != nil {
		return message.AppMessage{}, err
	}
	if appMessage.ID == "" {
		return message.AppMessage{}, notFound("message not found")
	}
	messages := []message.AppMessage{appMessage}
	if err := store.attachAppMessageReadDecorations(ctx, workspaceID, viewerMemberID, messages); err != nil {
		return message.AppMessage{}, err
	}
	return messages[0], nil
}

// reviewFeedbackContextSnapshot preserves the on-disk JSON shape of feedback
// metadata (reviewContextId / subject / target fields) so feedback messages
// remain readable. With the single-table model, the subject is the review
// context's thread.
func reviewFeedbackContextSnapshot(rc agent.ReviewContext) message.ReviewFeedbackContextSnapshot {
	subjectType := ""
	if strings.TrimSpace(rc.ThreadID) != "" {
		subjectType = "thread"
	}
	return message.ReviewFeedbackContextSnapshot{
		ReviewContextID: strings.TrimSpace(rc.ID),
		SubjectType:     subjectType,
		SubjectID:       strings.TrimSpace(rc.ThreadID),
		SourceAgentID:   strings.TrimSpace(rc.SourceAgentID),
		Kind:            "git_repo",
		DisplayName:     strings.TrimSpace(rc.DisplayName),
		RootPath:        strings.TrimSpace(rc.RootPath),
		HeadRef:         strings.TrimSpace(rc.HeadRef),
		BaseRef:         strings.TrimSpace(rc.BaseRef),
		PRURL:           strings.TrimSpace(rc.PRURL),
	}
}
