package message

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

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

func JsonParamExpression(runtime any, param int) string {
	_ = runtime
	return fmt.Sprintf("$%d", param)
}

func AllocateNextWorkspaceMessageSeq(ctx context.Context, tx coredb.Tx, workspaceID string) (int64, error) {
	commandTag, err := tx.Exec(ctx, `
UPDATE workspaces
SET latest_message_seq = latest_message_seq + 1
WHERE id = $1
`, workspaceID)
	if err != nil {
		return 0, fmt.Errorf("allocate global message seq: %w", err)
	}
	if commandTag.RowsAffected() == 0 {
		return 0, fmt.Errorf("allocate global message seq: workspace not found")
	}

	var globalSeq int64
	if err := tx.QueryRow(ctx, `
SELECT latest_message_seq
FROM workspaces
WHERE id = $1
`, workspaceID).Scan(&globalSeq); err != nil {
		return 0, fmt.Errorf("load allocated global message seq: %w", err)
	}
	return globalSeq, nil
}

func AllocateNextScopeMessageSeq(ctx context.Context, tx coredb.Tx, scopeID string) (int64, error) {
	commandTag, err := tx.Exec(ctx, `
UPDATE scopes
SET latest_seq = latest_seq + 1
WHERE id = $1
`, scopeID)
	if err != nil {
		return 0, fmt.Errorf("allocate scope message seq: %w", err)
	}
	if commandTag.RowsAffected() == 0 {
		return 0, fmt.Errorf("allocate scope message seq: scope not found")
	}

	var seq int64
	if err := tx.QueryRow(ctx, `
SELECT latest_seq
FROM scopes
WHERE id = $1
`, scopeID).Scan(&seq); err != nil {
		return 0, fmt.Errorf("load allocated scope message seq: %w", err)
	}
	return seq, nil
}

type ScopeMessageSeqAllocation struct {
	State     string
	Seq       int64
	LatestSeq int64
}

func AllocateNextScopeMessageSeqWithFreshness(ctx context.Context, tx coredb.Tx, scopeID string, seenUpToSeq int64) (ScopeMessageSeqAllocation, error) {
	commandTag, err := tx.Exec(ctx, `
UPDATE scopes
SET latest_seq = latest_seq + 1,
    updated_at = $3
WHERE id = $1
  AND latest_seq = $2
`, scopeID, seenUpToSeq, time.Now().UTC())
	if err != nil {
		return ScopeMessageSeqAllocation{}, fmt.Errorf("allocate scope message seq with freshness: %w", err)
	}
	if commandTag.RowsAffected() == 1 {
		seq := seenUpToSeq + 1
		return ScopeMessageSeqAllocation{State: SendStateSent, Seq: seq, LatestSeq: seq}, nil
	}

	latestSeq, err := loadScopeLatestSeq(ctx, tx, scopeID)
	if err != nil {
		return ScopeMessageSeqAllocation{}, fmt.Errorf("load held scope latest seq: %w", err)
	}
	return ScopeMessageSeqAllocation{State: SendStateHeld, LatestSeq: latestSeq}, nil
}

// ScopeMessageSeqVisibleFreshnessRequest keeps the normal freshness path as a
// write-only raw CAS. Only CAS misses pay for bounded visibility reads to decide
// whether the sender truly missed a visible message or only invisible tail
// messages. Raw scope sequence allocation remains global and monotonic.
type ScopeMessageSeqVisibleFreshnessRequest struct {
	ScopeID             string
	ViewerMemberID      string
	ViewerType          string
	SeenUpToSeq         int64
	UseVisibleMissCheck bool
	CandidateBatchLimit int
	MaxCandidateBatches int
}

func AllocateNextScopeMessageSeqWithVisibleFreshness(ctx context.Context, tx coredb.Tx, request ScopeMessageSeqVisibleFreshnessRequest) (ScopeMessageSeqAllocation, error) {
	scopeID := strings.TrimSpace(request.ScopeID)
	viewerMemberID := strings.TrimSpace(request.ViewerMemberID)
	if scopeID == "" || viewerMemberID == "" {
		return ScopeMessageSeqAllocation{}, fmt.Errorf("allocate scope message seq with visible freshness requires scope and viewer")
	}
	if request.SeenUpToSeq < 0 {
		return ScopeMessageSeqAllocation{}, fmt.Errorf("allocate scope message seq with visible freshness requires non-negative seen seq")
	}

	viewerType := strings.TrimSpace(request.ViewerType)
	if viewerType == "" {
		viewerType = ScopeActorHuman
	}
	allocated, seq, err := tryAllocateScopeMessageSeqAfterLatest(ctx, tx, scopeID, request.SeenUpToSeq)
	if err != nil {
		return ScopeMessageSeqAllocation{}, err
	}
	if allocated {
		return ScopeMessageSeqAllocation{State: SendStateSent, Seq: seq, LatestSeq: seq}, nil
	}

	if !request.UseVisibleMissCheck {
		latestSeq, err := loadScopeLatestSeq(ctx, tx, scopeID)
		if err != nil {
			return ScopeMessageSeqAllocation{}, fmt.Errorf("load held scope latest seq for visible freshness allocation: %w", err)
		}
		return ScopeMessageSeqAllocation{State: SendStateHeld, LatestSeq: latestSeq}, nil
	}

	messages := NewScopeRepositories(tx).Messages
	visibility := NewVisibilityFilter()
	for attempt := 0; attempt < 8; attempt++ {
		rawLatest, membershipVisibility, err := loadScopeLatestSeqAndMembershipVisibility(ctx, tx, scopeID, viewerMemberID)
		if err != nil {
			return ScopeMessageSeqAllocation{}, fmt.Errorf("load scope latest seq for visible freshness allocation: %w", err)
		}
		if membershipVisibility != "private" {
			return ScopeMessageSeqAllocation{State: SendStateHeld, LatestSeq: rawLatest}, nil
		}

		visibleLatest, err := loadVisibleLatestScopeMessageSeq(ctx, messages, visibility, VisibilityFilterRequest{
			ViewerMemberID:      viewerMemberID,
			ViewerType:          viewerType,
			ScopeID:             scopeID,
			RequestKind:         ScopeVisibilityRequestHistory,
			BeforeSeq:           rawLatest + 1,
			Limit:               1,
			CandidateBatchLimit: visibleFreshnessCandidateBatchLimit(request.CandidateBatchLimit),
			MaxCandidateBatches: visibleFreshnessMaxCandidateBatches(request.MaxCandidateBatches),
		})
		if err != nil {
			return ScopeMessageSeqAllocation{}, err
		}
		if request.SeenUpToSeq < visibleLatest {
			return ScopeMessageSeqAllocation{State: SendStateHeld, LatestSeq: visibleLatest}, nil
		}

		allocated, seq, err := tryAllocateScopeMessageSeqAfterLatest(ctx, tx, scopeID, rawLatest)
		if err != nil {
			return ScopeMessageSeqAllocation{}, err
		}
		if allocated {
			return ScopeMessageSeqAllocation{State: SendStateSent, Seq: seq, LatestSeq: seq}, nil
		}
	}
	return ScopeMessageSeqAllocation{}, fmt.Errorf("allocate scope message seq with visible freshness: concurrent allocation did not settle")
}

func visibleFreshnessCandidateBatchLimit(override int) int {
	if override > 0 {
		return visibilityCandidateBatchLimit(1, override)
	}
	return 16
}

func visibleFreshnessMaxCandidateBatches(override int) int {
	if override > 0 {
		return visibilityMaxCandidateBatches(override)
	}
	return 1
}

func loadVisibleLatestScopeMessageSeq(ctx context.Context, messages MessageStore, visibility VisibilityFilter, request VisibilityFilterRequest) (int64, error) {
	request.Limit = 1
	result, err := visibility.ListVisibleMessages(ctx, messages, request)
	if err != nil {
		return 0, fmt.Errorf("load visible latest scope message seq: %w", err)
	}
	if len(result.Messages) == 0 {
		return 0, nil
	}
	return result.Messages[0].Message.Seq, nil
}

func loadScopeLatestSeq(ctx context.Context, queryer coredb.Queryer, scopeID string) (int64, error) {
	var latestSeq int64
	if err := queryer.QueryRow(ctx, `
SELECT latest_seq
FROM scopes
WHERE id = $1
`, strings.TrimSpace(scopeID)).Scan(&latestSeq); err != nil {
		return 0, err
	}
	return latestSeq, nil
}

func loadScopeLatestSeqAndMembershipVisibility(ctx context.Context, queryer coredb.Queryer, scopeID string, memberID string) (int64, string, error) {
	var latestSeq int64
	var visibility string
	if err := queryer.QueryRow(ctx, `
SELECT s.latest_seq, COALESCE(sm.visibility, parent_private.visibility, '')
FROM scopes s
LEFT JOIN scope_memberships sm
  ON sm.scope_id = s.id
 AND sm.member_id = $2
 AND sm.removed_at IS NULL
 AND sm.archived_at IS NULL
 AND sm.effective_to_seq IS NULL
LEFT JOIN scopes parent_scope
  ON parent_scope.id = s.parent_scope_id
 AND parent_scope.workspace_id = s.workspace_id
 AND parent_scope.type = 'channel'
 AND parent_scope.status = 'active'
 AND parent_scope.archived_at IS NULL
LEFT JOIN scope_memberships parent_private
  ON s.type = 'thread'
 AND parent_private.scope_id = parent_scope.id
 AND parent_private.member_id = $2
 AND parent_private.visibility = 'private'
 AND parent_private.removed_at IS NULL
 AND parent_private.archived_at IS NULL
 AND parent_private.effective_to_seq IS NULL
WHERE s.id = $1
`, strings.TrimSpace(scopeID), strings.TrimSpace(memberID)).Scan(&latestSeq, &visibility); err != nil {
		return 0, "", err
	}
	return latestSeq, strings.TrimSpace(visibility), nil
}

func tryAllocateScopeMessageSeqAfterLatest(ctx context.Context, tx coredb.Tx, scopeID string, latestSeq int64) (bool, int64, error) {
	commandTag, err := tx.Exec(ctx, `
UPDATE scopes
SET latest_seq = latest_seq + 1,
    updated_at = $3
WHERE id = $1
  AND latest_seq = $2
`, strings.TrimSpace(scopeID), latestSeq, time.Now().UTC())
	if err != nil {
		return false, 0, fmt.Errorf("allocate scope message seq after latest: %w", err)
	}
	if commandTag.RowsAffected() == 0 {
		return false, 0, nil
	}
	return true, latestSeq + 1, nil
}

func AllocateNextScopeActivitySeq(ctx context.Context, tx coredb.Tx, scopeID string) (int64, error) {
	commandTag, err := tx.Exec(ctx, `
UPDATE scopes
SET latest_activity_seq = latest_activity_seq + 1
WHERE id = $1
`, scopeID)
	if err != nil {
		return 0, fmt.Errorf("allocate scope activity seq: %w", err)
	}
	if commandTag.RowsAffected() == 0 {
		return 0, fmt.Errorf("allocate scope activity seq: scope not found")
	}

	var seq int64
	if err := tx.QueryRow(ctx, `
SELECT latest_activity_seq
FROM scopes
WHERE id = $1
`, scopeID).Scan(&seq); err != nil {
		return 0, fmt.Errorf("load allocated scope activity seq: %w", err)
	}
	return seq, nil
}
