package message

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

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

type HistoryRangeReader struct {
	Asc  func(ctx context.Context, lowSeq int64, highSeq int64, limit int) ([]model.ThreadMessageRecord, error)
	Desc func(ctx context.Context, lowSeq int64, highSeq int64, limit int) ([]model.ThreadMessageRecord, error)
}

func (txs TxStore) ReadThreadHistoryRecords(ctx context.Context, workspaceID string, scopeID string, limit int, before *int64, after *int64) ([]model.ThreadMessageRecord, bool, error) {
	bounds, err := txs.loadScopeHistoryBounds(ctx, scopeID)
	if err != nil {
		return nil, false, err
	}
	return AssembleThreadHistoryRecords(
		ctx,
		bounds,
		limit,
		before,
		after,
		HistoryRangeReader{
			Asc: func(ctx context.Context, lowSeq int64, highSeq int64, limit int) ([]model.ThreadMessageRecord, error) {
				return txs.readColdScopeRecords(ctx, workspaceID, scopeID, lowSeq, highSeq, archive.ReadAscending, limit)
			},
			Desc: func(ctx context.Context, lowSeq int64, highSeq int64, limit int) ([]model.ThreadMessageRecord, error) {
				return txs.readColdScopeRecords(ctx, workspaceID, scopeID, lowSeq, highSeq, archive.ReadDescending, limit)
			},
		},
		HistoryRangeReader{
			Asc: func(ctx context.Context, lowSeq int64, highSeq int64, limit int) ([]model.ThreadMessageRecord, error) {
				return txs.readHotScopeRecords(ctx, scopeID, lowSeq, highSeq, true, limit)
			},
			Desc: func(ctx context.Context, lowSeq int64, highSeq int64, limit int) ([]model.ThreadMessageRecord, error) {
				return txs.readHotScopeRecords(ctx, scopeID, lowSeq, highSeq, false, limit)
			},
		},
	)
}

func (txs TxStore) loadScopeHistoryBounds(ctx context.Context, scopeID string) (model.ThreadHistoryBounds, error) {
	row := txs.tx.QueryRow(ctx, `
SELECT
    latest_seq,
    archived_until_seq
FROM scopes
WHERE id = $1
  AND status = 'active'
  AND archived_at IS NULL
`, scopeID)
	var bounds model.ThreadHistoryBounds
	if err := row.Scan(&bounds.LatestSeq, &bounds.ArchivedUntilSeq); err != nil {
		if coredb.IsNotFound(err) {
			return model.ThreadHistoryBounds{}, ErrNotFound
		}
		return model.ThreadHistoryBounds{}, fmt.Errorf("load scope history bounds: %w", err)
	}
	return bounds, nil
}

func (txs TxStore) readHotScopeRecords(ctx context.Context, scopeID string, lowSeq int64, highSeq int64, ascending bool, limit int) ([]model.ThreadMessageRecord, error) {
	if lowSeq > highSeq || limit <= 0 {
		return []model.ThreadMessageRecord{}, nil
	}
	order := "ASC"
	if !ascending {
		order = "DESC"
	}
	query := fmt.Sprintf(`
SELECT sm.id, sm.scope_id, scope.type, COALESCE(scope.parent_scope_id, ''), sm.seq, sm.workspace_global_seq, sm.author_member_id, sm.author_type, sm.content, sm.metadata, sm.created_at
FROM scope_messages sm
JOIN scopes scope
  ON scope.id = sm.scope_id
 AND scope.workspace_id = sm.workspace_id
 AND scope.status = 'active'
 AND scope.archived_at IS NULL
WHERE sm.scope_id = $1
  AND sm.seq >= $2
  AND sm.seq <= $3
  AND %s
ORDER BY sm.seq %s
LIMIT $4
`, HumanVisibleMessageTypeSQL("sm"), order)
	rows, err := txs.tx.Query(ctx, query, scopeID, lowSeq, highSeq, limit)
	if err != nil {
		return nil, fmt.Errorf("query hot thread messages: %w", err)
	}
	defer rows.Close()

	messages := make([]model.ThreadMessageRecord, 0, limit)
	for rows.Next() {
		var message model.ThreadMessageRecord
		var rawMetadata []byte
		if err := rows.Scan(&message.MessageID, &message.ScopeID, &message.ScopeKind, &message.ParentScopeID, &message.Seq, &message.WorkspaceGlobalSeq, &message.AuthorMemberID, &message.AuthorType, &message.Content, &rawMetadata, &message.CreatedAt); err != nil {
			return nil, fmt.Errorf("scan hot thread message: %w", err)
		}
		message.Metadata, err = DecodeMessageMetadata(rawMetadata)
		if err != nil {
			return nil, err
		}
		messages = append(messages, message)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate hot thread messages: %w", err)
	}
	return messages, nil
}

func (txs TxStore) readColdScopeRecords(ctx context.Context, workspaceID string, scopeID string, lowSeq int64, highSeq int64, direction archive.ReadDirection, limit int) ([]model.ThreadMessageRecord, error) {
	if lowSeq > highSeq || limit <= 0 {
		return []model.ThreadMessageRecord{}, nil
	}
	messages := make([]model.ThreadMessageRecord, 0, limit)
	batchLowSeq := lowSeq
	batchHighSeq := highSeq
	batchLimit := coldThreadReadBatchLimit(limit)
	for len(messages) < limit && batchLowSeq <= batchHighSeq {
		coldMessages, err := archive.ReadRange(ctx, txs.archiveStorage, workspaceID, scopeID, batchLowSeq, batchHighSeq, direction, batchLimit)
		if err != nil {
			return nil, err
		}
		if len(coldMessages) == 0 {
			break
		}
		for _, message := range coldMessages {
			metadata := CopyMessageMetadata(message.Metadata)
			messages = append(messages, model.ThreadMessageRecord{
				MessageID:          message.ID,
				Seq:                message.Seq,
				WorkspaceGlobalSeq: message.Seq,
				AuthorMemberID:     message.SenderName,
				AuthorType:         message.SenderType,
				Content:            message.Content,
				Metadata:           metadata,
				CreatedAt:          message.Timestamp,
			})
			if len(messages) == limit {
				break
			}
		}

		lastRawSeq := coldMessages[len(coldMessages)-1].Seq
		if direction == archive.ReadAscending {
			if lastRawSeq >= batchHighSeq {
				break
			}
			batchLowSeq = lastRawSeq + 1
			continue
		}
		if lastRawSeq <= batchLowSeq {
			break
		}
		batchHighSeq = lastRawSeq - 1
	}
	return messages, nil
}

func coldThreadReadBatchLimit(limit int) int {
	if limit <= 0 {
		return 0
	}
	if limit > int(archive.ChunkMessageCount) {
		return limit
	}
	return int(archive.ChunkMessageCount)
}

type OwnerPrivateContextVisibility struct {
	AlwaysVisibleAuthorMemberIDs map[string]struct{}
	PeopleGrants                 []ownerPrivateHistoryPeopleGrant
}

type ownerPrivateHistoryPeopleGrant struct {
	MemberID  string
	AddedAt   time.Time
	ExpiresAt *time.Time
	RemovedAt *time.Time
	LeftAt    *time.Time
}

type ownerPrivateHistorySourceScope struct {
	ScopeType string
	ScopeID   string
}

func (txs TxStore) LoadOwnerPrivateContextVisibility(ctx context.Context, workspaceID string, agentMemberID string, threadID string) (OwnerPrivateContextVisibility, error) {
	return loadOwnerPrivateContextVisibility(ctx, txs.tx, workspaceID, agentMemberID, threadID)
}

func loadOwnerPrivateContextVisibility(ctx context.Context, queryer coredb.Queryer, workspaceID string, agentMemberID string, threadID string) (OwnerPrivateContextVisibility, error) {
	scopes, err := loadOwnerPrivateHistorySourceScopes(ctx, queryer, workspaceID, threadID)
	if err != nil {
		return OwnerPrivateContextVisibility{}, err
	}
	scopeID, hostChannelID := ownerPrivateHistoryScopeAndHost(scopes)
	alwaysVisible, err := loadOwnerPrivateHistoryVisibleAuthorMemberIDs(ctx, queryer, workspaceID, agentMemberID, hostChannelID, scopeID)
	if err != nil {
		return OwnerPrivateContextVisibility{}, err
	}
	people, err := loadOwnerPrivateHistoryPeopleGrants(ctx, queryer, workspaceID, agentMemberID, scopes)
	if err != nil {
		return OwnerPrivateContextVisibility{}, err
	}
	return OwnerPrivateContextVisibility{
		AlwaysVisibleAuthorMemberIDs: alwaysVisible,
		PeopleGrants:                 people,
	}, nil
}

func ownerPrivateHistoryScopeAndHost(scopes []ownerPrivateHistorySourceScope) (string, string) {
	if len(scopes) == 0 {
		return "", ""
	}
	scopeID := strings.TrimSpace(scopes[0].ScopeID)
	hostChannelID := scopeID
	if strings.TrimSpace(scopes[0].ScopeType) == "thread" {
		hostChannelID = ""
		for _, scope := range scopes[1:] {
			if strings.TrimSpace(scope.ScopeType) == "channel" {
				hostChannelID = strings.TrimSpace(scope.ScopeID)
				break
			}
		}
	}
	return scopeID, hostChannelID
}

func loadOwnerPrivateHistoryVisibleAuthorMemberIDs(ctx context.Context, queryer coredb.Queryer, workspaceID string, agentMemberID string, hostChannelID string, scopeID string) (map[string]struct{}, error) {
	visible := make(map[string]struct{}, 8)
	trimmedAgentMemberID := strings.TrimSpace(agentMemberID)
	if trimmedAgentMemberID != "" {
		visible[trimmedAgentMemberID] = struct{}{}
	}

	rows, err := queryer.Query(ctx, `
WITH current_private_context AS (
    SELECT sm.owner_member_id, sm.scope_id AS host_channel_id
    FROM scope_memberships sm
    JOIN scopes host_scope
      ON host_scope.id = sm.scope_id
     AND host_scope.workspace_id = $1
     AND host_scope.type = 'channel'
     AND host_scope.status = 'active'
     AND host_scope.archived_at IS NULL
    WHERE sm.member_id = $2
      AND sm.visibility = 'private'
      AND sm.owner_member_id IS NOT NULL
      AND sm.removed_at IS NULL
      AND sm.archived_at IS NULL
      AND sm.effective_to_seq IS NULL
      AND ($3 = '' OR sm.scope_id = $3)
    ORDER BY sm.created_at DESC, sm.effective_from_seq DESC
    LIMIT 1
)
SELECT owner_member_id
FROM current_private_context
UNION
SELECT sibling_private.member_id
FROM current_private_context cpc
JOIN scope_memberships sibling_private
  ON sibling_private.scope_id = cpc.host_channel_id
 AND sibling_private.owner_member_id = cpc.owner_member_id
 AND sibling_private.visibility = 'private'
 AND sibling_private.removed_at IS NULL
 AND sibling_private.archived_at IS NULL
 AND sibling_private.effective_to_seq IS NULL
JOIN agents sibling_agent
  ON sibling_agent.workspace_id = $1
 AND sibling_agent.member_id = sibling_private.member_id
JOIN scope_memberships sibling_membership
  ON sibling_membership.scope_id = $4
 AND sibling_membership.member_id = sibling_private.member_id
 AND sibling_membership.removed_at IS NULL
 AND sibling_membership.archived_at IS NULL
 AND sibling_membership.effective_to_seq IS NULL
`, strings.TrimSpace(workspaceID), trimmedAgentMemberID, strings.TrimSpace(hostChannelID), strings.TrimSpace(scopeID))
	if err != nil {
		return nil, fmt.Errorf("query owner-private history visible authors: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var memberID string
		if err := rows.Scan(&memberID); err != nil {
			return nil, fmt.Errorf("scan owner-private history visible author: %w", err)
		}
		if trimmedMemberID := strings.TrimSpace(memberID); trimmedMemberID != "" {
			visible[trimmedMemberID] = struct{}{}
		}
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate owner-private history visible authors: %w", err)
	}

	return visible, nil
}

func loadOwnerPrivateHistorySourceScopes(ctx context.Context, queryer coredb.Queryer, workspaceID string, scopeID string) ([]ownerPrivateHistorySourceScope, error) {
	row := queryer.QueryRow(ctx, `
SELECT scope.type, scope.id, COALESCE(parent_scope.type, ''), COALESCE(parent_scope.id, '')
FROM scopes scope
LEFT JOIN scopes parent_scope
  ON parent_scope.workspace_id = scope.workspace_id
 AND parent_scope.id = scope.parent_scope_id
 AND parent_scope.status = 'active'
 AND parent_scope.archived_at IS NULL
WHERE scope.workspace_id = $1
  AND scope.id = $2
  AND scope.status = 'active'
  AND scope.archived_at IS NULL
`, strings.TrimSpace(workspaceID), strings.TrimSpace(scopeID))
	var scopeType string
	var loadedScopeID string
	var parentScopeType string
	var parentScopeID string
	if err := row.Scan(&scopeType, &loadedScopeID, &parentScopeType, &parentScopeID); err != nil {
		if coredb.IsNotFound(err) {
			return nil, ErrNotFound
		}
		return nil, fmt.Errorf("load owner-private history scope: %w", err)
	}
	loadedScopeID = strings.TrimSpace(loadedScopeID)
	switch strings.TrimSpace(scopeType) {
	case "dm":
		return nil, nil
	case "channel":
		return []ownerPrivateHistorySourceScope{{
			ScopeType: "channel",
			ScopeID:   loadedScopeID,
		}}, nil
	case "thread":
		scopes := []ownerPrivateHistorySourceScope{{
			ScopeType: "thread",
			ScopeID:   loadedScopeID,
		}}
		if strings.TrimSpace(parentScopeType) == "channel" {
			scopes = append(scopes, ownerPrivateHistorySourceScope{
				ScopeType: "channel",
				ScopeID:   strings.TrimSpace(parentScopeID),
			})
		}
		return scopes, nil

	}
	return nil, nil
}

func ownerPrivateHistoryScopeClause(args *[]any, scopes []ownerPrivateHistorySourceScope, tableAlias string) string {
	clauses := make([]string, 0, len(scopes))
	seen := make(map[string]struct{}, len(scopes))
	for _, scope := range scopes {
		scopeType := strings.TrimSpace(scope.ScopeType)
		scopeID := strings.TrimSpace(scope.ScopeID)
		if scopeType == "" || scopeID == "" {
			continue
		}
		key := scopeType + "\x00" + scopeID
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		scopeTypeArg := len(*args) + 1
		*args = append(*args, scopeType)
		scopeIDArg := len(*args) + 1
		*args = append(*args, scopeID)
		clauses = append(clauses, fmt.Sprintf("(%s.scope_type = $%d AND %s.scope_id = $%d)", tableAlias, scopeTypeArg, tableAlias, scopeIDArg))
	}
	return strings.Join(clauses, " OR ")
}

func loadOwnerPrivateHistoryPeopleGrants(ctx context.Context, queryer coredb.Queryer, workspaceID string, agentMemberID string, scopes []ownerPrivateHistorySourceScope) ([]ownerPrivateHistoryPeopleGrant, error) {
	if len(scopes) == 0 {
		return nil, nil
	}
	args := []any{strings.TrimSpace(workspaceID), strings.TrimSpace(agentMemberID)}
	scopeClause := ownerPrivateHistoryScopeClause(&args, scopes, "tse")
	if scopeClause == "" {
		return nil, nil
	}
	query := `
WITH owner_machine AS (
    SELECT machines.created_by_user_id
    FROM agents
    JOIN machines
      ON machines.workspace_id = agents.workspace_id
     AND machines.id = agents.machine_id
    WHERE agents.workspace_id = $1
      AND agents.member_id = $2
      AND agents.status <> 'archived'
      AND machines.status = 'active'
),
owner_member AS (
    SELECT members.id
    FROM members
    JOIN owner_machine
      ON owner_machine.created_by_user_id = members.user_id
    WHERE members.workspace_id = $1
      AND members.type = 'human'
)
SELECT tspm.member_id, tspm.added_at, tspm.expires_at, tspm.removed_at, tspm.left_at
FROM task_scope_executions tse
JOIN owner_member
  ON owner_member.id = tse.owner_member_id
JOIN task_scope_participant_metadata tspm
  ON tspm.owner_scope_execution_id = tse.id
WHERE tse.workspace_id = $1
  AND (` + scopeClause + `)
ORDER BY tspm.added_at ASC, tspm.id ASC
`
	rows, err := queryer.Query(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("query owner-private history people grants: %w", err)
	}
	defer rows.Close()

	grants := make([]ownerPrivateHistoryPeopleGrant, 0)
	for rows.Next() {
		var grant ownerPrivateHistoryPeopleGrant
		if err := rows.Scan(&grant.MemberID, &grant.AddedAt, &grant.ExpiresAt, &grant.RemovedAt, &grant.LeftAt); err != nil {
			return nil, fmt.Errorf("scan owner-private history people grant: %w", err)
		}
		grants = append(grants, grant)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate owner-private history people grants: %w", err)
	}
	return grants, nil
}

func AssembleThreadHistoryRecords(ctx context.Context, bounds model.ThreadHistoryBounds, limit int, before *int64, after *int64, coldReader HistoryRangeReader, hotReader HistoryRangeReader) ([]model.ThreadMessageRecord, bool, error) {
	if bounds.LatestSeq == 0 {
		return []model.ThreadMessageRecord{}, false, nil
	}

	fetchLimit := limit + 1
	if fetchLimit <= 0 {
		fetchLimit = 51
	}

	if after != nil {
		records, err := assembleAscendingThreadRecords(ctx, bounds, *after, fetchLimit, coldReader, hotReader)
		if err != nil {
			return nil, false, err
		}
		hasMore := len(records) > limit
		if hasMore {
			records = records[:limit]
		}
		return records, hasMore, nil
	}

	records, err := assembleDescendingThreadRecords(ctx, bounds, before, fetchLimit, coldReader, hotReader)
	if err != nil {
		return nil, false, err
	}
	hasMore := len(records) > limit
	if hasMore {
		records = records[:limit]
	}
	ReverseThreadMessageRecords(records)
	return records, hasMore, nil
}

func assembleAscendingThreadRecords(ctx context.Context, bounds model.ThreadHistoryBounds, afterSeq int64, limit int, coldReader HistoryRangeReader, hotReader HistoryRangeReader) ([]model.ThreadMessageRecord, error) {
	lowSeq := afterSeq + 1
	if lowSeq < 1 {
		lowSeq = 1
	}
	if lowSeq > bounds.LatestSeq || limit <= 0 {
		return []model.ThreadMessageRecord{}, nil
	}

	messages := make([]model.ThreadMessageRecord, 0, limit)
	if lowSeq <= bounds.ArchivedUntilSeq {
		coldHighSeq := bounds.ArchivedUntilSeq
		if coldHighSeq > bounds.LatestSeq {
			coldHighSeq = bounds.LatestSeq
		}
		coldMessages, err := coldReader.Asc(ctx, lowSeq, coldHighSeq, limit)
		if err != nil {
			return nil, err
		}
		messages = append(messages, coldMessages...)
	}
	if len(messages) == limit {
		return messages, nil
	}

	hotLowSeq := lowSeq
	if hotLowSeq <= bounds.ArchivedUntilSeq {
		hotLowSeq = bounds.ArchivedUntilSeq + 1
	}
	if hotLowSeq > bounds.LatestSeq {
		return messages, nil
	}
	hotMessages, err := hotReader.Asc(ctx, hotLowSeq, bounds.LatestSeq, limit-len(messages))
	if err != nil {
		return nil, err
	}
	messages = append(messages, hotMessages...)
	return messages, nil
}

func assembleDescendingThreadRecords(ctx context.Context, bounds model.ThreadHistoryBounds, before *int64, limit int, coldReader HistoryRangeReader, hotReader HistoryRangeReader) ([]model.ThreadMessageRecord, error) {
	upperSeq := bounds.LatestSeq
	if before != nil {
		upperSeq = *before - 1
	}
	if upperSeq > bounds.LatestSeq {
		upperSeq = bounds.LatestSeq
	}
	if upperSeq < 1 || limit <= 0 {
		return []model.ThreadMessageRecord{}, nil
	}

	messages := make([]model.ThreadMessageRecord, 0, limit)
	if upperSeq > bounds.ArchivedUntilSeq {
		hotMessages, err := hotReader.Desc(ctx, bounds.ArchivedUntilSeq+1, upperSeq, limit)
		if err != nil {
			return nil, err
		}
		messages = append(messages, hotMessages...)
	}
	if len(messages) == limit {
		return messages, nil
	}

	coldHighSeq := upperSeq
	if coldHighSeq > bounds.ArchivedUntilSeq {
		coldHighSeq = bounds.ArchivedUntilSeq
	}
	if coldHighSeq < 1 {
		return messages, nil
	}
	coldMessages, err := coldReader.Desc(ctx, 1, coldHighSeq, limit-len(messages))
	if err != nil {
		return nil, err
	}
	messages = append(messages, coldMessages...)
	return messages, nil
}

func ReverseThreadMessageRecords(records []model.ThreadMessageRecord) {
	for left, right := 0, len(records)-1; left < right; left, right = left+1, right-1 {
		records[left], records[right] = records[right], records[left]
	}
}

func OwnerPrivateHistoryBatchLimit(limit int) int {
	if limit <= 0 {
		return 50
	}
	batch := limit * 4
	if batch < 50 {
		return 50
	}
	if batch > 500 {
		return 500
	}
	return batch
}

func OwnerPrivateContextHistoryRecordVisible(record model.ThreadMessageRecord, visibility OwnerPrivateContextVisibility) bool {
	if _, ok := visibility.AlwaysVisibleAuthorMemberIDs[record.AuthorMemberID]; ok {
		return true
	}
	for _, grant := range visibility.PeopleGrants {
		if strings.TrimSpace(grant.MemberID) != strings.TrimSpace(record.AuthorMemberID) {
			continue
		}
		if record.CreatedAt.Before(grant.AddedAt.Truncate(time.Second)) {
			continue
		}
		if grant.ExpiresAt != nil && !record.CreatedAt.Before(*grant.ExpiresAt) {
			continue
		}
		if grant.RemovedAt != nil && !record.CreatedAt.Before(*grant.RemovedAt) {
			continue
		}
		if grant.LeftAt != nil && !record.CreatedAt.Before(*grant.LeftAt) {
			continue
		}
		return true
	}
	return false
}
