package onboarding

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

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

type VisibleReplyEvidence struct {
	WorkspaceID    string
	AgentID        string
	MessageID      string
	ReplyMessageID string
	// CompleteImmediately is reserved for the server-authored Desktop mock
	// exchange, whose persisted reply is itself the completion boundary.
	CompleteImmediately bool
}

type visibleReplyMatch struct {
	userID            string
	tutorialChannelID string
	replyScopeID      string
	replySeq          int64
}

// RecordVisibleReply validates and records one qualifying human-to-Agent
// tutorial exchange. Runtime claims are treated only as lookup keys.
func (store *Store) RecordVisibleReply(ctx context.Context, evidence VisibleReplyEvidence) error {
	if store == nil || store.db == nil {
		return fmt.Errorf("onboarding store is not configured")
	}
	workspaceID := strings.TrimSpace(evidence.WorkspaceID)
	agentID := strings.TrimSpace(evidence.AgentID)
	messageID := strings.TrimSpace(evidence.MessageID)
	replyMessageID := strings.TrimSpace(evidence.ReplyMessageID)
	if workspaceID == "" || agentID == "" || messageID == "" || replyMessageID == "" {
		return nil
	}

	return coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		match, ok, err := loadVisibleReplyMatch(ctx, tx, workspaceID, agentID, messageID, replyMessageID)
		if err != nil {
			return err
		}
		if !ok {
			return nil
		}

		locked, err := loadState(ctx, tx, match.userID, workspaceID, true)
		if err != nil {
			if errors.Is(err, ErrStateNotFound) {
				return nil
			}
			return err
		}
		if locked.Status == StatusCompleted || locked.TutorialChannelID != match.tutorialChannelID {
			return nil
		}
		now := time.Now().UTC()
		progress := locked.Progress
		progress.ReplyMessageID = replyMessageID
		progress.ReplyScopeID = match.replyScopeID
		progress.ReplySeq = match.replySeq
		status := StatusActive
		currentStep := StepWaitReply
		completedAt := locked.CompletedAt
		if evidence.CompleteImmediately || progress.VisibleThroughSeq >= match.replySeq {
			status = StatusCompleted
			currentStep = StepDone
			completedAt = &now
		}
		progressJSON, err := marshalProgress(progress)
		if err != nil {
			return err
		}
		_, err = tx.Exec(ctx, `
UPDATE desktop_onboarding_states
SET progress_json = $3,
    status = $4,
    current_step = $5,
    completed_at = $6,
    updated_at = $7
WHERE user_id = $1
  AND workspace_id = $2
  AND flow_version = $8
  AND status = 'active'
`, match.userID, workspaceID, progressJSON, status, currentStep, completedAt, now, locked.FlowVersion)
		if err != nil {
			return fmt.Errorf("record onboarding visible reply: %w", err)
		}
		return nil
	})
}

func loadVisibleReplyMatch(ctx context.Context, queryer coredb.QueryRower, workspaceID string, agentID string, messageID string, replyMessageID string) (visibleReplyMatch, bool, error) {
	var match visibleReplyMatch
	var metadataJSON []byte
	err := queryer.QueryRow(ctx, `
SELECT origin_author.user_id, channel_scope.id, origin.scope_id, reply.seq, origin.metadata
FROM scope_messages origin
JOIN members origin_author
  ON origin_author.id = origin.author_member_id
 AND origin_author.workspace_id = origin.workspace_id
 AND origin_author.type = 'human'
 AND origin_author.status = 'active'
JOIN scopes origin_scope
  ON origin_scope.id = origin.scope_id
 AND origin_scope.workspace_id = origin.workspace_id
 AND origin_scope.type IN ('channel', 'thread')
 AND origin_scope.status = 'active'
 AND origin_scope.archived_at IS NULL
LEFT JOIN scopes parent_channel_scope
  ON origin_scope.type = 'thread'
 AND parent_channel_scope.id = origin_scope.parent_scope_id
 AND parent_channel_scope.workspace_id = origin_scope.workspace_id
 AND parent_channel_scope.type = 'channel'
 AND parent_channel_scope.status = 'active'
 AND parent_channel_scope.archived_at IS NULL
JOIN scopes channel_scope
  ON channel_scope.id = CASE
    WHEN origin_scope.type = 'thread' THEN parent_channel_scope.id
    ELSE origin_scope.id
  END
 AND channel_scope.workspace_id = origin.workspace_id
 AND channel_scope.type = 'channel'
 AND channel_scope.status = 'active'
 AND channel_scope.archived_at IS NULL
JOIN scope_channel_profiles channel_profile
  ON channel_profile.scope_id = channel_scope.id
 AND channel_profile.workspace_id = channel_scope.workspace_id
 AND channel_profile.subsystem IN ('im', 'team', 'tutorial')
JOIN agents mentioned_agent
  ON mentioned_agent.id = $2
 AND mentioned_agent.workspace_id = origin.workspace_id
 AND mentioned_agent.status <> 'archived'
JOIN scope_messages reply
  ON reply.id = $4
 AND reply.workspace_id = origin.workspace_id
 AND reply.scope_id = origin.scope_id
 AND reply.author_type = 'agent'
 AND reply.author_member_id = mentioned_agent.member_id
WHERE origin.workspace_id = $1
  AND origin.id = $3
  AND origin.author_type = 'human'
LIMIT 1
`, workspaceID, agentID, messageID, replyMessageID).Scan(
		&match.userID,
		&match.tutorialChannelID,
		&match.replyScopeID,
		&match.replySeq,
		&metadataJSON,
	)
	if err != nil {
		if coredb.IsNotFound(err) {
			return visibleReplyMatch{}, false, nil
		}
		return visibleReplyMatch{}, false, fmt.Errorf("validate onboarding visible reply: %w", err)
	}
	var metadata map[string]any
	if len(metadataJSON) == 0 || model.UnmarshalJSONObject("onboarding origin message metadata", metadataJSON, &metadata) != nil || !metadataMentionsAgent(metadata, agentID) {
		return visibleReplyMatch{}, false, nil
	}
	return match, true, nil
}

func metadataMentionsAgent(metadata map[string]any, agentID string) bool {
	for _, mention := range message.ExtractPersistedMentionMetadata(metadata) {
		if mention.Type == "agent" && strings.TrimSpace(mention.ID) == agentID {
			return true
		}
	}
	return false
}

// AcknowledgeVisibleReply records the strongest sequence that the mounted
// channel surface says it rendered, after verifying the authoritative cursor.
func (store *Store) AcknowledgeVisibleReply(ctx context.Context, userID string, workspaceID string, scopeID string, replyMessageID string) (State, error) {
	if store == nil || store.db == nil {
		return State{}, fmt.Errorf("onboarding store is not configured")
	}
	var state State
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		locked, err := loadState(ctx, tx, userID, workspaceID, true)
		if err != nil {
			if errors.Is(err, ErrStateNotFound) {
				return nil
			}
			return err
		}
		if locked.Status == StatusCompleted ||
			locked.Progress.ReplyMessageID == "" ||
			locked.Progress.ReplyMessageID != strings.TrimSpace(replyMessageID) ||
			locked.Progress.ReplyScopeID != strings.TrimSpace(scopeID) {
			state = locked
			return nil
		}
		var replySeq, lastReadSeq int64
		err = tx.QueryRow(ctx, `
SELECT reply.seq, membership.last_read_seq
FROM scope_messages reply
JOIN scopes reply_scope
  ON reply_scope.id = reply.scope_id
 AND reply_scope.workspace_id = reply.workspace_id
 AND reply_scope.type IN ('channel', 'thread')
 AND reply_scope.status = 'active'
 AND reply_scope.archived_at IS NULL
 AND (
   reply_scope.id = $5
   OR reply_scope.parent_scope_id = $5
 )
JOIN scope_memberships membership
  ON membership.scope_id = reply.scope_id
 AND membership.member_id = $2
 AND membership.removed_at IS NULL
 AND membership.archived_at IS NULL
 AND membership.effective_to_seq IS NULL
WHERE reply.workspace_id = $1
  AND reply.scope_id = $3
  AND reply.id = $4
  AND reply.author_type = 'agent'
LIMIT 1
`, strings.TrimSpace(workspaceID), locked.OwnerMemberID, strings.TrimSpace(scopeID), strings.TrimSpace(replyMessageID), locked.TutorialChannelID).Scan(&replySeq, &lastReadSeq)
		if err != nil {
			if coredb.IsNotFound(err) {
				state = locked
				return nil
			}
			return fmt.Errorf("validate onboarding reply visibility: %w", err)
		}
		if lastReadSeq < replySeq {
			state = locked
			return nil
		}
		now := time.Now().UTC()
		progress := locked.Progress
		if replySeq > progress.VisibleThroughSeq {
			progress.VisibleThroughSeq = replySeq
		}
		progressJSON, err := marshalProgress(progress)
		if err != nil {
			return err
		}
		_, err = tx.Exec(ctx, `
UPDATE desktop_onboarding_states
SET progress_json = $3,
    status = 'completed',
    current_step = 'done',
    completed_at = COALESCE(completed_at, $4),
    updated_at = $4
WHERE user_id = $1
  AND workspace_id = $2
  AND flow_version = $5
`, strings.TrimSpace(userID), strings.TrimSpace(workspaceID), progressJSON, now, locked.FlowVersion)
		if err != nil {
			return fmt.Errorf("acknowledge onboarding reply visibility: %w", err)
		}
		state, err = loadState(ctx, tx, userID, workspaceID, false)
		return err
	})
	return state, err
}
