package memory

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"
	"unicode/utf8"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

// FindMemoryCheckpointByContentVersion resolves one exact product projection.
// The lookup is fully scoped and never enumerates the Memory history.
func (s *Store) FindMemoryCheckpointByContentVersion(ctx context.Context, workspaceID, memoryID, contentID, contentVersionID string) (MemoryContentCheckpoint, bool, error) {
	if err := s.ensureDB(); err != nil {
		return MemoryContentCheckpoint{}, false, err
	}
	var checkpoint MemoryContentCheckpoint
	err := s.db.WithContext(ctx).
		Where("workspace_id = ? AND memory_id = ? AND content_id = ? AND content_version_id = ?", strings.TrimSpace(workspaceID), strings.TrimSpace(memoryID), strings.TrimSpace(contentID), strings.TrimSpace(contentVersionID)).
		First(&checkpoint).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return MemoryContentCheckpoint{}, false, nil
	}
	return checkpoint, err == nil, err
}

// FindMemoryCheckpointByOperation resolves the durable Memory operation receipt
// used to make projection finalization idempotent after Content Store publish.
func (s *Store) FindMemoryCheckpointByOperation(ctx context.Context, workspaceID, memoryID, operationID string) (MemoryContentCheckpoint, bool, error) {
	if err := s.ensureDB(); err != nil {
		return MemoryContentCheckpoint{}, false, err
	}
	var checkpoint MemoryContentCheckpoint
	err := s.db.WithContext(ctx).
		Where("workspace_id = ? AND memory_id = ? AND operation_id = ?", strings.TrimSpace(workspaceID), strings.TrimSpace(memoryID), strings.TrimSpace(operationID)).
		First(&checkpoint).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return MemoryContentCheckpoint{}, false, nil
	}
	return checkpoint, err == nil, err
}

// CreateMemoryContentCheckpoint records one exact immutable ContentVersion
// projection. Exact replays are idempotent; changed facts fail closed.
func (s *Store) CreateMemoryContentCheckpoint(ctx context.Context, checkpoint MemoryContentCheckpoint) (MemoryContentCheckpoint, error) {
	if err := s.ensureDB(); err != nil {
		return MemoryContentCheckpoint{}, err
	}
	normalizeMemoryCheckpoint(&checkpoint)
	if err := validateMemoryCheckpoint(checkpoint); err != nil {
		return MemoryContentCheckpoint{}, err
	}
	if err := s.db.WithContext(ctx).Clauses(clause.OnConflict{DoNothing: true}).Create(&checkpoint).Error; err != nil {
		return MemoryContentCheckpoint{}, fmt.Errorf("create memory content checkpoint: %w", err)
	}
	stored, found, err := s.FindMemoryCheckpointByContentVersion(ctx, checkpoint.WorkspaceID, checkpoint.MemoryID, checkpoint.ContentID, checkpoint.ContentVersionID)
	if err != nil {
		return MemoryContentCheckpoint{}, fmt.Errorf("load memory content checkpoint: %w", err)
	}
	if !found || !sameMemoryCheckpoint(stored, checkpoint) {
		return MemoryContentCheckpoint{}, ErrMemoryContentConflict
	}
	return stored, nil
}

// AdvanceMemoryContentCheckpoint atomically advances the Memory cloud head and
// acknowledgement cursor from the caller's exact prior projection.
func (s *Store) AdvanceMemoryContentCheckpoint(ctx context.Context, workspaceID, memoryID, expectedContentID, expectedVersionID string, expectedGeneration uint64, checkpoint MemoryContentCheckpoint, updatedBy string, updatedAt time.Time) (Memory, error) {
	if err := s.ensureDB(); err != nil {
		return Memory{}, err
	}
	normalizeMemoryCheckpoint(&checkpoint)
	workspaceID = strings.TrimSpace(workspaceID)
	memoryID = strings.TrimSpace(memoryID)
	updatedBy = strings.TrimSpace(updatedBy)
	if err := validateMemoryCheckpoint(checkpoint); err != nil {
		return Memory{}, err
	}
	if workspaceID == "" || memoryID == "" || expectedGeneration == 0 || updatedBy == "" || updatedAt.IsZero() || checkpoint.MemoryID != memoryID || checkpoint.WorkspaceID != workspaceID {
		return Memory{}, fmt.Errorf("memory content advance is invalid")
	}
	query := s.db.WithContext(ctx).Model(&Memory{}).
		Where("id = ? AND deleted_at IS NULL AND content_id = ? AND active_content_version_id = ? AND content_generation = ? AND content_history_sequence = ?", memoryID, strings.TrimSpace(expectedContentID), strings.TrimSpace(expectedVersionID), expectedGeneration, checkpoint.HistorySequence-1)
	updates := map[string]any{
		"content_id": checkpoint.ContentID, "active_content_version_id": checkpoint.ContentVersionID,
		"content_history_sequence": checkpoint.HistorySequence,
		"updated_by":               updatedBy, "updated_at": updatedAt,
	}
	if checkpoint.OperationKind == "checkpoint" {
		query = query.Where("acknowledged_sequence = ? AND (active_replica_id = '' OR active_replica_id = ?)", checkpoint.CoveredFromSequence-1, checkpoint.ReplicaID)
		updates["active_replica_id"] = checkpoint.ReplicaID
		updates["acknowledged_sequence"] = checkpoint.CoveredThroughSequence
	}
	result := query.Updates(updates)
	if result.Error != nil {
		return Memory{}, fmt.Errorf("advance memory content checkpoint: %w", result.Error)
	}
	if result.RowsAffected != 1 {
		return Memory{}, ErrMemoryContentConflict
	}
	return s.GetMemory(ctx, workspaceID, memoryID)
}

// FinalizeMemoryContentImport installs the fully verified imported branch head
// into a previously unbound Memory projection. Exact retries are idempotent;
// any competing lineage, version, or generation fails closed.
func (s *Store) FinalizeMemoryContentImport(ctx context.Context, workspaceID, memoryID, contentID, headVersionID string, generation, historySequence uint64, updatedBy string, updatedAt time.Time) (Memory, error) {
	if err := s.ensureDB(); err != nil {
		return Memory{}, err
	}
	workspaceID = strings.TrimSpace(workspaceID)
	memoryID = strings.TrimSpace(memoryID)
	contentID = strings.TrimSpace(contentID)
	headVersionID = strings.TrimSpace(headVersionID)
	updatedBy = strings.TrimSpace(updatedBy)
	if workspaceID == "" || memoryID == "" || contentID == "" || headVersionID == "" || generation == 0 || historySequence == 0 || updatedBy == "" || updatedAt.IsZero() {
		return Memory{}, fmt.Errorf("memory content import finalization is invalid")
	}
	checkpoint, found, err := s.FindMemoryCheckpointByContentVersion(ctx, workspaceID, memoryID, contentID, headVersionID)
	if err != nil {
		return Memory{}, err
	}
	if !found || checkpoint.OperationKind != "import" || checkpoint.Generation != generation || checkpoint.HistorySequence > historySequence {
		return Memory{}, ErrMemoryContentConflict
	}
	var latest MemoryContentCheckpoint
	if err := s.db.WithContext(ctx).Select("history_sequence").
		Where("workspace_id = ? AND memory_id = ?", workspaceID, memoryID).
		Order("history_sequence DESC").First(&latest).Error; err != nil {
		return Memory{}, fmt.Errorf("load memory content history sequence: %w", err)
	}
	if latest.HistorySequence != historySequence {
		return Memory{}, ErrMemoryContentConflict
	}
	current, err := s.GetMemory(ctx, workspaceID, memoryID)
	if err != nil {
		return Memory{}, err
	}
	if current.ContentID == contentID && current.ActiveContentVersionID == headVersionID && current.ContentGeneration == generation && current.ContentHistorySequence == historySequence {
		return current, nil
	}
	if current.ContentID != "" || current.ActiveContentVersionID != "" || current.ContentGeneration != 0 || current.ContentHistorySequence != 0 {
		return Memory{}, ErrMemoryContentConflict
	}
	result := s.db.WithContext(ctx).Model(&Memory{}).
		Where("id = ? AND deleted_at IS NULL AND content_id = '' AND active_content_version_id = '' AND content_generation = 0 AND content_history_sequence = 0", memoryID).
		Updates(map[string]any{
			"content_id": contentID, "active_content_version_id": headVersionID, "content_generation": generation,
			"content_history_sequence": historySequence,
			"updated_by":               updatedBy, "updated_at": updatedAt,
		})
	if result.Error != nil {
		return Memory{}, fmt.Errorf("finalize memory content import: %w", result.Error)
	}
	if result.RowsAffected != 1 {
		return Memory{}, ErrMemoryContentConflict
	}
	return s.GetMemory(ctx, workspaceID, memoryID)
}

// ListMemoryContentCheckpoints returns one bounded page plus a single lookahead
// row, ordered stably without counting or hydrating Content Store payloads.
func (s *Store) ListMemoryContentCheckpoints(ctx context.Context, workspaceID, memoryID string, limit, offset int) ([]MemoryContentCheckpoint, error) {
	if err := s.ensureDB(); err != nil {
		return nil, err
	}
	if limit <= 0 || limit > 100 || offset < 0 || offset > 10_000 {
		return nil, fmt.Errorf("memory content history page is out of bounds")
	}
	var checkpoints []MemoryContentCheckpoint
	if err := s.db.WithContext(ctx).
		Where("workspace_id = ? AND memory_id = ?", strings.TrimSpace(workspaceID), strings.TrimSpace(memoryID)).
		Order("history_sequence DESC").Order("id DESC").Limit(limit + 1).Offset(offset).Find(&checkpoints).Error; err != nil {
		return nil, fmt.Errorf("list memory content checkpoints: %w", err)
	}
	return checkpoints, nil
}

func normalizeMemoryCheckpoint(checkpoint *MemoryContentCheckpoint) {
	checkpoint.ID = strings.TrimSpace(checkpoint.ID)
	checkpoint.WorkspaceID = strings.TrimSpace(checkpoint.WorkspaceID)
	checkpoint.MemoryID = strings.TrimSpace(checkpoint.MemoryID)
	checkpoint.ContentID = strings.TrimSpace(checkpoint.ContentID)
	checkpoint.ContentVersionID = strings.TrimSpace(checkpoint.ContentVersionID)
	checkpoint.ParentContentVersionID = strings.TrimSpace(checkpoint.ParentContentVersionID)
	checkpoint.TargetContentVersionID = strings.TrimSpace(checkpoint.TargetContentVersionID)
	checkpoint.OperationID = strings.TrimSpace(checkpoint.OperationID)
	checkpoint.OperationKind = strings.TrimSpace(checkpoint.OperationKind)
	checkpoint.ReplicaID = strings.TrimSpace(checkpoint.ReplicaID)
	checkpoint.RootDigest = strings.TrimSpace(checkpoint.RootDigest)
	checkpoint.SourceAuthorName = strings.TrimSpace(checkpoint.SourceAuthorName)
	checkpoint.SourceAuthorEmail = strings.TrimSpace(checkpoint.SourceAuthorEmail)
	if checkpoint.SourceAuthoredAt != nil {
		authoredAt := checkpoint.SourceAuthoredAt.UTC()
		checkpoint.SourceAuthoredAt = &authoredAt
	}
	checkpoint.CreatedBy = strings.TrimSpace(checkpoint.CreatedBy)
}

func validateMemoryCheckpoint(checkpoint MemoryContentCheckpoint) error {
	if checkpoint.ID == "" || checkpoint.WorkspaceID == "" || checkpoint.MemoryID == "" || checkpoint.ContentID == "" || checkpoint.ContentVersionID == "" || checkpoint.OperationID == "" || checkpoint.Generation == 0 || checkpoint.HistorySequence == 0 || checkpoint.RootDigest == "" || checkpoint.FileCount < 0 || checkpoint.SizeBytes < 0 || checkpoint.CreatedBy == "" || checkpoint.CreatedAt.IsZero() {
		return fmt.Errorf("memory content checkpoint is invalid")
	}
	if len(checkpoint.SourceAuthorName) > 255 || len(checkpoint.SourceAuthorEmail) > 255 || len(checkpoint.SourceMessage) > 64<<10 ||
		!utf8.ValidString(checkpoint.SourceAuthorName) || !utf8.ValidString(checkpoint.SourceAuthorEmail) || !utf8.ValidString(checkpoint.SourceMessage) ||
		strings.ContainsRune(checkpoint.SourceAuthorName, '\x00') || strings.ContainsRune(checkpoint.SourceAuthorEmail, '\x00') || strings.ContainsRune(checkpoint.SourceMessage, '\x00') {
		return fmt.Errorf("memory content source metadata exceeds bounds")
	}
	switch checkpoint.OperationKind {
	case "checkpoint":
		if checkpoint.ReplicaID == "" || checkpoint.CoveredFromSequence == 0 || checkpoint.CoveredThroughSequence < checkpoint.CoveredFromSequence {
			return fmt.Errorf("memory checkpoint acknowledgement is invalid")
		}
		if checkpoint.SourceAuthoredAt != nil || checkpoint.SourceAuthorName != "" || checkpoint.SourceAuthorEmail != "" || checkpoint.SourceMessage != "" {
			return fmt.Errorf("memory checkpoint cannot carry migration source metadata")
		}
	case "restore":
		if checkpoint.CoveredFromSequence != 0 || checkpoint.CoveredThroughSequence != 0 {
			return fmt.Errorf("memory non-checkpoint operation cannot acknowledge local sequence")
		}
		if checkpoint.SourceAuthoredAt != nil || checkpoint.SourceAuthorName != "" || checkpoint.SourceAuthorEmail != "" || checkpoint.SourceMessage != "" {
			return fmt.Errorf("memory restore cannot carry migration source metadata")
		}
	case "import":
		if checkpoint.CoveredFromSequence != 0 || checkpoint.CoveredThroughSequence != 0 || checkpoint.SourceAuthoredAt == nil || checkpoint.SourceAuthoredAt.IsZero() || !checkpoint.CreatedAt.Equal(*checkpoint.SourceAuthoredAt) {
			return fmt.Errorf("memory import source metadata is invalid")
		}
	default:
		return fmt.Errorf("memory content operation kind is invalid")
	}
	return nil
}

func sameMemoryCheckpoint(left, right MemoryContentCheckpoint) bool {
	return left.WorkspaceID == right.WorkspaceID && left.MemoryID == right.MemoryID && left.ContentID == right.ContentID && left.ContentVersionID == right.ContentVersionID &&
		left.ParentContentVersionID == right.ParentContentVersionID && left.TargetContentVersionID == right.TargetContentVersionID && left.OperationID == right.OperationID && left.OperationKind == right.OperationKind && left.ReplicaID == right.ReplicaID &&
		left.CoveredFromSequence == right.CoveredFromSequence && left.CoveredThroughSequence == right.CoveredThroughSequence && left.Generation == right.Generation &&
		left.HistorySequence == right.HistorySequence &&
		left.RootDigest == right.RootDigest && left.FileCount == right.FileCount && left.SizeBytes == right.SizeBytes &&
		left.SourceAuthorName == right.SourceAuthorName && left.SourceAuthorEmail == right.SourceAuthorEmail && sameOptionalTime(left.SourceAuthoredAt, right.SourceAuthoredAt) && left.SourceMessage == right.SourceMessage
}

func sameOptionalTime(left, right *time.Time) bool {
	if left == nil || right == nil {
		return left == right
	}
	return left.Equal(*right)
}
