package memory

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

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/service/content"
	memorystore "github.com/tidbcloud/AgentTeam/internal/store/resource/memory"
)

const maxMemoryContentHistoryOffset = 10_000

type MemoryCheckpointMutation struct {
	OperationID            string
	ReplicaID              string
	ExpectedVersionID      string
	Generation             uint64
	CoveredFromSequence    uint64
	CoveredThroughSequence uint64
	Files                  []NativeMemoryContentFile
}

type MemoryRestoreMutation struct {
	OperationID       string
	ExpectedVersionID string
	TargetVersionID   string
	Generation        uint64
}

type MemoryBackupMutation struct {
	ExpectedVersionID string
	Generation        uint64
	Label             string
}

type MemoryContentVersion struct {
	VersionID              string
	ParentVersionID        string
	TargetVersionID        string
	OperationID            string
	OperationKind          string
	ReplicaID              string
	CoveredFromSequence    uint64
	CoveredThroughSequence uint64
	Generation             uint64
	RootDigest             string
	FileCount              int
	SizeBytes              int64
	SourceAuthorName       string
	SourceAuthorEmail      string
	SourceMessage          string
	CreatedBy              string
	CreatedAt              time.Time
}

type MemoryContentVersionPage struct {
	Items   []MemoryContentVersion
	Page    int
	PerPage int
	HasMore bool
}

type MemoryContentCompareEntry struct {
	Path         string
	Change       string
	BeforeDigest string
	AfterDigest  string
}

type MemoryContentBackup struct {
	ID        string
	VersionID string
	Label     string
	CreatedBy string
	CreatedAt time.Time
}

type MemoryContentArchive struct {
	Body      io.ReadCloser
	VersionID string
	FileCount int
	SizeBytes int64
}

func (s *MemoryService) PublishMemoryCheckpoint(ctx context.Context, workspaceID, subject, memoryID string, input MemoryCheckpointMutation) (MemoryContentVersion, error) {
	memory, request, err := s.nativeMemoryBinding(ctx, workspaceID, subject, memoryID, true)
	if err != nil {
		return MemoryContentVersion{}, err
	}
	if !memory.CanManage {
		return MemoryContentVersion{}, forbidden("memory checkpoint requires manage access")
	}
	if strings.TrimSpace(input.ReplicaID) == "" || input.CoveredFromSequence == 0 || input.CoveredThroughSequence < input.CoveredFromSequence {
		return MemoryContentVersion{}, invalidArgument("memory checkpoint sequence range is invalid")
	}
	existing, replay, err := s.store.FindMemoryCheckpointByOperation(ctx, strings.TrimSpace(workspaceID), memory.ID, strings.TrimSpace(input.OperationID))
	if err != nil {
		return MemoryContentVersion{}, err
	}
	if replay {
		if existing.OperationKind != "checkpoint" || existing.ParentContentVersionID != strings.TrimSpace(input.ExpectedVersionID) || existing.Generation != input.Generation || existing.ReplicaID != strings.TrimSpace(input.ReplicaID) || existing.CoveredFromSequence != input.CoveredFromSequence || existing.CoveredThroughSequence != input.CoveredThroughSequence {
			return MemoryContentVersion{}, conflict("memory checkpoint operation was reused with different inputs")
		}
	} else {
		if strings.TrimSpace(input.ExpectedVersionID) != memory.ActiveContentVersionID || input.Generation != memory.ContentGeneration {
			return MemoryContentVersion{}, conflict("memory cloud head or generation is stale")
		}
		if input.CoveredFromSequence != memory.AcknowledgedSequence+1 || (memory.ActiveReplicaID != "" && memory.ActiveReplicaID != strings.TrimSpace(input.ReplicaID)) {
			return MemoryContentVersion{}, conflict("memory checkpoint replica or sequence is stale")
		}
	}
	request.VersionID = strings.TrimSpace(input.ExpectedVersionID)
	result, err := s.nativeContent.PublishCheckpoint(ctx, NativeMemoryCheckpointRequest{
		NativeMemoryContentRequest: request,
		OperationID:                strings.TrimSpace(input.OperationID), ReplicaID: strings.TrimSpace(input.ReplicaID),
		CoveredFromSequence: input.CoveredFromSequence, CoveredThroughSequence: input.CoveredThroughSequence,
		Files: input.Files,
	})
	if err != nil {
		return MemoryContentVersion{}, err
	}
	return s.finalizeNativeMemoryVersion(ctx, memory, subject, result, memorystore.MemoryContentCheckpoint{
		WorkspaceID: strings.TrimSpace(workspaceID), MemoryID: memory.ID,
		OperationID: strings.TrimSpace(input.OperationID), OperationKind: "checkpoint", ReplicaID: strings.TrimSpace(input.ReplicaID),
		CoveredFromSequence: input.CoveredFromSequence, CoveredThroughSequence: input.CoveredThroughSequence,
	})
}

func (s *MemoryService) ListMemoryContentVersions(ctx context.Context, workspaceID, subject, memoryID string, page, perPage int) (MemoryContentVersionPage, error) {
	offset, ok := checkedMemoryContentPageOffset(page, perPage)
	if !ok {
		return MemoryContentVersionPage{}, invalidArgument("memory content history page is out of bounds")
	}
	memory, _, err := s.nativeMemoryBinding(ctx, workspaceID, subject, memoryID, false)
	if err != nil {
		return MemoryContentVersionPage{}, err
	}
	rows, err := s.store.ListMemoryContentCheckpoints(ctx, strings.TrimSpace(workspaceID), memory.ID, perPage, offset)
	if err != nil {
		return MemoryContentVersionPage{}, mapMemoryContentStoreError(err)
	}
	hasMore := len(rows) > perPage
	if hasMore {
		rows = rows[:perPage]
	}
	items := make([]MemoryContentVersion, 0, len(rows))
	for _, row := range rows {
		items = append(items, memoryContentVersionFromStore(row))
	}
	return MemoryContentVersionPage{Items: items, Page: page, PerPage: perPage, HasMore: hasMore}, nil
}

func (s *MemoryService) CompareMemoryContentVersions(ctx context.Context, workspaceID, subject, memoryID, beforeVersionID, afterVersionID string) ([]MemoryContentCompareEntry, error) {
	_, request, err := s.nativeMemoryBinding(ctx, workspaceID, subject, memoryID, false)
	if err != nil {
		return nil, err
	}
	entries, err := s.nativeContent.Compare(ctx, request, beforeVersionID, afterVersionID)
	if err != nil {
		return nil, err
	}
	result := make([]MemoryContentCompareEntry, 0, len(entries))
	for _, entry := range entries {
		result = append(result, MemoryContentCompareEntry{Path: entry.Path, Change: entry.Change, BeforeDigest: entry.BeforeDigest, AfterDigest: entry.AfterDigest})
	}
	return result, nil
}

func (s *MemoryService) CreateMemoryContentBackup(ctx context.Context, workspaceID, subject, memoryID string, input MemoryBackupMutation) (MemoryContentBackup, error) {
	memory, request, err := s.nativeMemoryBinding(ctx, workspaceID, subject, memoryID, false)
	if err != nil {
		return MemoryContentBackup{}, err
	}
	if !memory.CanManage {
		return MemoryContentBackup{}, forbidden("memory backup requires manage access")
	}
	if strings.TrimSpace(input.ExpectedVersionID) != memory.ActiveContentVersionID || input.Generation != memory.ContentGeneration {
		return MemoryContentBackup{}, conflict("memory cloud head or generation is stale")
	}
	request.VersionID = memory.ActiveContentVersionID
	pin, err := s.nativeContent.CreateBackup(ctx, request, input.Label)
	if err != nil {
		return MemoryContentBackup{}, err
	}
	return memoryContentBackupFromNative(pin), nil
}

func (s *MemoryService) ListMemoryContentBackups(ctx context.Context, workspaceID, subject, memoryID string, limit int) ([]MemoryContentBackup, error) {
	_, request, err := s.nativeMemoryBinding(ctx, workspaceID, subject, memoryID, false)
	if err != nil {
		return nil, err
	}
	pins, err := s.nativeContent.ListBackups(ctx, request, limit)
	if err != nil {
		return nil, err
	}
	result := make([]MemoryContentBackup, 0, len(pins))
	for _, pin := range pins {
		result = append(result, memoryContentBackupFromNative(pin))
	}
	return result, nil
}

func (s *MemoryService) RestoreMemoryContentVersion(ctx context.Context, workspaceID, subject, memoryID string, input MemoryRestoreMutation) (MemoryContentVersion, error) {
	memory, request, err := s.nativeMemoryBinding(ctx, workspaceID, subject, memoryID, true)
	if err != nil {
		return MemoryContentVersion{}, err
	}
	if !memory.CanManage {
		return MemoryContentVersion{}, forbidden("memory restore requires manage access")
	}
	existing, replay, err := s.store.FindMemoryCheckpointByOperation(ctx, strings.TrimSpace(workspaceID), memory.ID, strings.TrimSpace(input.OperationID))
	if err != nil {
		return MemoryContentVersion{}, err
	}
	if replay {
		if existing.OperationKind != "restore" || existing.ParentContentVersionID != strings.TrimSpace(input.ExpectedVersionID) || existing.TargetContentVersionID != strings.TrimSpace(input.TargetVersionID) || existing.Generation != input.Generation {
			return MemoryContentVersion{}, conflict("memory restore operation was reused with different inputs")
		}
	} else if strings.TrimSpace(input.ExpectedVersionID) != memory.ActiveContentVersionID || input.Generation != memory.ContentGeneration {
		return MemoryContentVersion{}, conflict("memory cloud head or generation is stale")
	}
	request.VersionID = strings.TrimSpace(input.ExpectedVersionID)
	result, err := s.nativeContent.Restore(ctx, request, input.TargetVersionID, input.OperationID)
	if err != nil {
		return MemoryContentVersion{}, err
	}
	return s.finalizeNativeMemoryVersion(ctx, memory, subject, result, memorystore.MemoryContentCheckpoint{
		WorkspaceID: strings.TrimSpace(workspaceID), MemoryID: memory.ID,
		OperationID: strings.TrimSpace(input.OperationID), OperationKind: "restore", TargetContentVersionID: strings.TrimSpace(input.TargetVersionID),
	})
}

func (s *MemoryService) OpenMemoryContentArchive(ctx context.Context, workspaceID, subject, memoryID, versionID string) (MemoryContentArchive, error) {
	_, request, err := s.nativeMemoryBinding(ctx, workspaceID, subject, memoryID, false)
	if err != nil {
		return MemoryContentArchive{}, err
	}
	body, meta, err := s.nativeContent.OpenArchive(ctx, request, versionID)
	if err != nil {
		return MemoryContentArchive{}, err
	}
	return MemoryContentArchive{Body: body, VersionID: strings.TrimSpace(versionID), FileCount: meta.FileCount, SizeBytes: meta.SizeBytes}, nil
}

func (s *MemoryService) nativeMemoryBinding(ctx context.Context, workspaceID, subject, memoryID string, allowHeadDivergence bool) (memorystore.Memory, NativeMemoryContentRequest, error) {
	if s == nil || s.nativeContent == nil || s.store == nil {
		return memorystore.Memory{}, NativeMemoryContentRequest{}, invalidArgument("native memory content is not configured")
	}
	memory, err := s.GetMemory(ctx, workspaceID, subject, memoryID)
	if err != nil {
		return memorystore.Memory{}, NativeMemoryContentRequest{}, err
	}
	native, err := s.nativeMemoryRead(ctx, workspaceID, memory.ID)
	if err != nil {
		return memorystore.Memory{}, NativeMemoryContentRequest{}, err
	}
	if !native {
		return memorystore.Memory{}, NativeMemoryContentRequest{}, invalidArgument("native memory content is not active")
	}
	if memory.ContentID == "" || memory.ActiveContentVersionID == "" || memory.ContentGeneration == 0 || memory.ContentHistorySequence == 0 {
		return memorystore.Memory{}, NativeMemoryContentRequest{}, conflict("memory exact content projection is incomplete")
	}
	resolved, err := s.nativeContent.Resolve(ctx, strings.TrimSpace(workspaceID), memory.ID, strings.TrimSpace(subject))
	if err != nil {
		return memorystore.Memory{}, NativeMemoryContentRequest{}, err
	}
	if resolved.ContentID != memory.ContentID || resolved.Generation != memory.ContentGeneration || (!allowHeadDivergence && resolved.VersionID != memory.ActiveContentVersionID) {
		return memorystore.Memory{}, NativeMemoryContentRequest{}, conflict("memory exact content projection does not match cloud lineage")
	}
	resolved.VersionID = memory.ActiveContentVersionID
	return memory, resolved, nil
}

func (s *MemoryService) finalizeNativeMemoryVersion(ctx context.Context, memory memorystore.Memory, subject string, result NativeMemoryVersionResult, checkpoint memorystore.MemoryContentCheckpoint) (MemoryContentVersion, error) {
	checkpointID, err := identifiers.NewEntityID("mcp")
	if err != nil {
		return MemoryContentVersion{}, fmt.Errorf("generate memory content checkpoint id: %w", err)
	}
	checkpoint.ID = checkpointID
	checkpoint.ContentID = result.ContentID
	checkpoint.ContentVersionID = result.VersionID
	checkpoint.ParentContentVersionID = result.ParentVersionID
	checkpoint.Generation = result.Generation
	checkpoint.RootDigest = result.RootDigest
	checkpoint.FileCount = result.FileCount
	checkpoint.SizeBytes = result.SizeBytes
	checkpoint.CreatedBy = strings.TrimSpace(subject)
	checkpoint.CreatedAt = result.CreatedAt

	var projected memorystore.MemoryContentCheckpoint
	err = s.store.Transaction(ctx, func(tx *memorystore.Store) error {
		existing, found, err := tx.FindMemoryCheckpointByOperation(ctx, checkpoint.WorkspaceID, checkpoint.MemoryID, checkpoint.OperationID)
		if err != nil {
			return err
		}
		if found {
			checkpoint.HistorySequence = existing.HistorySequence
			if !sameNativeMemoryCheckpoint(existing, checkpoint) {
				return memorystore.ErrMemoryContentConflict
			}
			current, err := tx.GetMemory(ctx, checkpoint.WorkspaceID, checkpoint.MemoryID)
			if err != nil {
				return err
			}
			if current.ContentID != result.ContentID || current.ActiveContentVersionID != result.VersionID || current.ContentGeneration != result.Generation || current.ContentHistorySequence != existing.HistorySequence {
				return memorystore.ErrMemoryContentConflict
			}
			projected = existing
			return nil
		}
		if memory.ContentHistorySequence == math.MaxUint64 {
			return memorystore.ErrMemoryContentConflict
		}
		checkpoint.HistorySequence = memory.ContentHistorySequence + 1
		if _, err := tx.AdvanceMemoryContentCheckpoint(ctx, checkpoint.WorkspaceID, checkpoint.MemoryID, memory.ContentID, memory.ActiveContentVersionID, memory.ContentGeneration, checkpoint, subject, s.now()); err != nil {
			return err
		}
		projected, err = tx.CreateMemoryContentCheckpoint(ctx, checkpoint)
		return err
	})
	if err != nil {
		return MemoryContentVersion{}, mapMemoryContentStoreError(err)
	}
	return memoryContentVersionFromStore(projected), nil
}

func checkedMemoryContentPageOffset(page, perPage int) (int, bool) {
	if page <= 0 || perPage <= 0 || perPage > memoryContentHistoryLimit {
		return 0, false
	}
	pageIndex := page - 1
	if pageIndex > maxMemoryContentHistoryOffset/perPage {
		return 0, false
	}
	offset := pageIndex * perPage
	return offset, offset >= 0 && offset <= maxMemoryContentHistoryOffset && offset <= math.MaxInt-perPage
}

func mapMemoryContentStoreError(err error) error {
	if errors.Is(err, memorystore.ErrMemoryContentConflict) {
		return conflict("memory cloud head, generation, replica, or operation is stale")
	}
	return mapStoreNotFound(err)
}

func memoryContentVersionFromStore(row memorystore.MemoryContentCheckpoint) MemoryContentVersion {
	return MemoryContentVersion{
		VersionID: row.ContentVersionID, ParentVersionID: row.ParentContentVersionID, TargetVersionID: row.TargetContentVersionID,
		OperationID: row.OperationID, OperationKind: row.OperationKind, ReplicaID: row.ReplicaID,
		CoveredFromSequence: row.CoveredFromSequence, CoveredThroughSequence: row.CoveredThroughSequence,
		Generation: row.Generation, RootDigest: row.RootDigest, FileCount: row.FileCount, SizeBytes: row.SizeBytes,
		SourceAuthorName: row.SourceAuthorName, SourceAuthorEmail: row.SourceAuthorEmail, SourceMessage: row.SourceMessage,
		CreatedBy: row.CreatedBy, CreatedAt: row.CreatedAt,
	}
}

func memoryContentBackupFromNative(pin content.Pin) MemoryContentBackup {
	return MemoryContentBackup{ID: pin.ID, VersionID: pin.VersionID, Label: pin.Label, CreatedBy: pin.CreatedBy, CreatedAt: pin.CreatedAt}
}

func sameNativeMemoryCheckpoint(left, right memorystore.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 && left.SourceMessage == right.SourceMessage
}
