package dispatcher

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"log/slog"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	gittransport "github.com/go-git/go-git/v5/plumbing/transport/http"
	"github.com/tidbcloud/AgentTeam/desktop/internal/memorywatcher"
	"github.com/tidbcloud/AgentTeam/desktop/internal/migrations"
	"github.com/tidbcloud/AgentTeam/desktop/internal/nativememory"
	"github.com/tidbcloud/AgentTeam/desktop/internal/vault"
)

var existingLocalMemoryImportMaxBytes int64 = 10 << 20

const (
	existingLocalMemoryImportMarker = ".agentteam-memory-import.json"
	existingLocalMemoryImportSchema = 1
)

type existingLocalMemoryImportState struct {
	SchemaVersion int    `json:"schemaVersion"`
	MemoryID      string `json:"memoryId"`
	AgentID       string `json:"agentId"`
	Branch        string `json:"branch"`
	IncludeRoot   bool   `json:"includeRoot"`
}

type legacyUserPreferencesMigrationFinalization struct {
	workDir              string
	agentID              string
	memoryID             string
	branch               string
	generalImportedBytes int64
	clone                *vault.Clone
	pushAuth             gittransport.AuthMethod
}

func newLegacyUserPreferencesMigrationFinalization(workDir, agentID string, clone *vault.Clone, pushAuth gittransport.AuthMethod, generalImportedBytes int64) *legacyUserPreferencesMigrationFinalization {
	if clone == nil {
		return nil
	}
	return &legacyUserPreferencesMigrationFinalization{
		workDir:              workDir,
		agentID:              agentID,
		memoryID:             clone.MemoryID(),
		branch:               clone.Branch(),
		generalImportedBytes: generalImportedBytes,
		clone:                clone,
		pushAuth:             pushAuth,
	}
}

func prepareLegacyUserPreferencesCutover(ctx context.Context, workDir, agentID string, clone *vault.Clone, pushAuth gittransport.AuthMethod, generalImportedBytes int64) *legacyUserPreferencesMigrationFinalization {
	finalization := newLegacyUserPreferencesMigrationFinalization(workDir, agentID, clone, pushAuth, generalImportedBytes)
	if finalization == nil {
		return nil
	}
	if err := markLegacyUserPreferencesMigrationReady(workDir, agentID, finalization.memoryID, finalization.branch, generalImportedBytes); err != nil {
		slog.WarnContext(ctx, "memory import: persist legacy user preference cutover state failed",
			"agent_id", agentID, "error", err)
		return nil
	}
	return finalization
}

func (f *legacyUserPreferencesMigrationFinalization) finalize(ctx context.Context, clone *vault.Clone, watcher *memorywatcher.Watcher, canMerge bool) (bool, error) {
	if f == nil {
		return true, nil
	}
	if ctx == nil {
		ctx = context.Background()
	}
	if clone == nil {
		clone = f.clone
	}
	if clone == nil || clone.MemoryID() != f.memoryID || strings.TrimSpace(clone.Branch()) != strings.TrimSpace(f.branch) {
		return false, errors.New("legacy user preference migration: source clone binding changed before cutover")
	}

	legacyPath := filepath.Join(f.workDir, "kb", "user-preferences.md")
	remainingBytes := existingLocalMemoryImportMaxBytes - f.generalImportedBytes
	if remainingBytes < 0 {
		remainingBytes = 0
	}
	content, ok, overLimit, err := readImportSource(legacyPath, remainingBytes)
	if err != nil {
		return false, err
	}
	if overLimit {
		slog.WarnContext(ctx, "memory import: legacy user preference exceeds remaining import budget; retaining Local Knowledge source",
			"agent_id", f.agentID, "remaining_bytes", remainingBytes)
		return true, markLegacyUserPreferencesMigrationComplete(f.workDir, f.agentID, f.memoryID, f.branch)
	}
	if ok {
		if !canMerge {
			return false, nil
		}
		apply := func() error {
			_, err := mergeLegacyUserPreferences(filepath.Join(clone.Path(), "profiles", "user.md"), content)
			return err
		}
		if watcher != nil {
			if err := watcher.ApplyAndFlush(apply); err != nil {
				return false, err
			}
		} else {
			if err := apply(); err != nil {
				return false, err
			}
			commitMsg := fmt.Sprintf("chore: import existing local memory\n\nImported existing local memory content for agent %s.", f.agentID)
			if _, err := clone.CommitPending(ctx, commitMsg, vault.CommitPendingOpts{}); err != nil && !errors.Is(err, vault.ErrNothingToCommit) {
				return false, err
			}
			pushCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
			_, err := clone.Push(pushCtx, vault.PushOpts{Auth: f.pushAuth})
			cancel()
			if err != nil && !errors.Is(err, vault.ErrNothingToPush) {
				return false, err
			}
		}
	}
	return true, markLegacyUserPreferencesMigrationComplete(f.workDir, f.agentID, f.memoryID, f.branch)
}

func (d *memoryService) finalizePendingLegacyPreferencesMigration(ctx context.Context, sess *interactiveSession, processRetired bool) error {
	if sess == nil {
		return nil
	}
	if ctx == nil {
		ctx = context.Background()
	}
	sess.mu.Lock()
	if !processRetired && (sess.process == nil || sess.processUsesWorkspaceRootMemory) {
		sess.mu.Unlock()
		return nil
	}
	migration := sess.pendingLegacyPreferencesMigration
	clone := sess.memoryClone
	watcher := sess.watcher
	sess.pendingLegacyPreferencesMigration = nil
	sess.mu.Unlock()
	if migration == nil {
		return nil
	}
	completed, err := migration.finalize(ctx, clone, watcher, processRetired)
	if err != nil || !completed {
		sess.mu.Lock()
		if sess.pendingLegacyPreferencesMigration == nil {
			sess.pendingLegacyPreferencesMigration = migration
		}
		sess.mu.Unlock()
	}
	if err != nil {
		slog.WarnContext(ctx, "memory import: close legacy user preference migration failed",
			"agent_id", sess.agentID, "error", err)
		return err
	}
	if !completed {
		return errors.New("legacy user preference migration remains pending")
	}
	return nil
}

// finalizeLegacyPreferencesBeforeSpawn closes the one-time workspace-root
// migration before the authoritative Process can dispatch its initial turn. A
// previous Process is detached from the published session and synchronously
// stopped first, making Stop the final writer barrier for both the legacy source
// and the resolved profile.
func (d *memoryService) finalizeLegacyPreferencesBeforeSpawn(
	ctx context.Context,
	sess, previous *interactiveSession,
) error {
	if sess == nil {
		return nil
	}
	sess.mu.Lock()
	pending := sess.pendingLegacyPreferencesMigration != nil
	sess.mu.Unlock()
	if !pending {
		return nil
	}

	if previous != nil && previous != sess {
		previous.mu.Lock()
		process := previous.process
		if process != nil {
			// The current start reservation already fences new deliveries. Clear
			// the published pointer before Stop so its onExit cannot start a second
			// continuation generation while this replacement owns publication.
			previous.process = nil
			previous.restartRetirementInFlight = true
		}
		previous.mu.Unlock()
		if process != nil {
			revokeSessionMCPToken(previous)
			d.revokeExecProcessAuthorization(process)
			process.Stop()
		}
	}

	if err := d.finalizePendingLegacyPreferencesMigration(ctx, sess, true); err != nil {
		return fmt.Errorf("finalize legacy user preferences before runtime spawn: %w", err)
	}
	return nil
}

// ensureMemoryCloneForAccess prepares the local clone and imports pre-binding
// content. On failure it restores any staged local root and creates a pending
// source root so a newly launched runtime still compiles the right prompt.
func (d *memoryService) ensureMemoryCloneForAccess(ctx context.Context, agentID, workDir string, access MemoryAccess) (*vault.Clone, *legacyUserPreferencesMigrationFinalization, *nativememory.Sync, bool) {
	target := filepath.Join(workDir, "memory")
	if access.Protocol == MemorySyncProtocolNativeContentV1 {
		if access.NativeRemote == nil {
			d.setMemorySyncStatus(agentID, degradedMemorySyncStatus(memorywatcher.StatusReasonCloneFailed))
			return nil, nil, nil, false
		}
		clone, syncer, err := openNativeMemoryJournal(ctx, agentID, workDir, access)
		if err != nil {
			status := degradedMemorySyncStatus(memorywatcher.StatusReasonCloneFailed)
			if errors.Is(err, nativememory.ErrRemoteHeadChanged) {
				status = memorywatcher.Status{State: memorywatcher.StatusPendingSync, Reason: memorywatcher.StatusReasonPushFailed}
			}
			d.setMemorySyncStatus(agentID, status)
			slog.WarnContext(ctx, "native memory: open local journal failed", "agent_id", agentID, "memory_id", access.MemoryID, "error", err)
			return nil, nil, nil, false
		}
		return clone, nil, syncer, true
	}
	if access.Protocol != MemorySyncProtocolLegacyGitV1 {
		d.setMemorySyncStatus(agentID, degradedMemorySyncStatus(memorywatcher.StatusReasonLookupFailed))
		return nil, nil, nil, false
	}
	preparedImport, err := prepareExistingLocalMemoryDirForImport(ctx, workDir, agentID, access)
	if err != nil {
		d.setMemorySyncStatus(agentID, degradedMemorySyncStatus(memorywatcher.StatusReasonCloneFailed))
		slog.WarnContext(ctx, "memory clone: prepare existing local memory import failed",
			"agent_id", agentID, "memory_id", access.MemoryID, "error", err)
		return nil, nil, nil, false
	}
	clone, err := vault.EnsureClone(ctx, vault.EnsureCloneOpts{
		MemoryID: access.MemoryID, AgentID: agentID, RepoURL: access.OriginURL,
		Branch: access.Branch, TargetPath: target, Auth: access.Auth,
	})
	if err != nil {
		restorePreparedExistingLocalMemoryDir(ctx, preparedImport, agentID)
		if pendingErr := ensurePendingLocalMemoryRoot(workDir, agentID, access); pendingErr != nil {
			slog.WarnContext(ctx, "memory clone: create pending local root failed",
				"agent_id", agentID, "memory_id", access.MemoryID, "error", pendingErr)
		}
		d.setMemorySyncStatus(agentID, degradedMemorySyncStatus(memorywatcher.FailureReason(err, memorywatcher.StatusReasonCloneFailed)))
		slog.WarnContext(ctx, "memory clone: clone failed",
			"agent_id", agentID, "memory_id", access.MemoryID, "error", err)
		return nil, nil, nil, false
	}
	var migration *legacyUserPreferencesMigrationFinalization
	if access.ImportExistingLocalContent || preparedImport != nil {
		migration = importExistingLocalMemoryIntoClone(ctx, workDir, preparedImport, clone, agentID, access.Auth)
	}
	return clone, migration, nil, true
}

type preparedExistingLocalMemoryDir struct {
	originalPath string
	stagedPath   string
	pending      bool
	includeRoot  bool
}

type existingLocalMemoryImportSource struct {
	rel                string
	filePath           string
	content            []byte
	removeAfterImport  bool
	skipIfDestNonEmpty bool
}

func prepareExistingLocalMemoryDirForImport(ctx context.Context, workDir, agentID string, access MemoryAccess) (*preparedExistingLocalMemoryDir, error) {
	memoryDir := filepath.Join(workDir, "memory")
	if info, err := os.Stat(filepath.Join(memoryDir, ".git")); err == nil && info.IsDir() {
		return findRetainedExistingLocalMemoryImport(ctx, workDir, agentID, access)
	} else if err != nil && !os.IsNotExist(err) {
		return nil, fmt.Errorf("memory import: stat source .git: %w", err)
	}
	retainedImportState, err := readExistingLocalMemoryImportState(memoryDir)
	if err != nil {
		return nil, fmt.Errorf("memory import: read retained import state: %w", err)
	}
	if retainedImportState != nil && !existingLocalMemoryImportStateMatches(*retainedImportState, agentID, access) {
		isolatedPath, err := isolatePendingLocalMemoryRoot(workDir)
		if err != nil {
			return nil, fmt.Errorf("memory import: isolate retained import from different binding: %w", err)
		}
		slog.InfoContext(ctx, "memory import: isolated retained import from a different binding",
			"agent_id", agentID,
			"memory_id", access.MemoryID,
			"retained_memory_id", retainedImportState.MemoryID,
			"path", isolatedPath)
		return nil, nil
	}
	pendingState, err := readPendingLocalMemoryState(memoryDir)
	if err != nil {
		return nil, err
	}
	if pendingState != nil && !pendingLocalMemoryStateMatches(*pendingState, agentID, access) {
		isolatedPath, err := isolatePendingLocalMemoryRoot(workDir)
		if err != nil {
			return nil, fmt.Errorf("memory import: isolate pending root from different binding: %w", err)
		}
		slog.InfoContext(ctx, "memory import: isolated pending root from a different binding",
			"agent_id", agentID,
			"memory_id", access.MemoryID,
			"pending_memory_id", pendingState.MemoryID,
			"path", isolatedPath)
		return nil, nil
	}
	retained, err := findRetainedExistingLocalMemoryImport(ctx, workDir, agentID, access)
	if err != nil || retained != nil {
		return retained, err
	}
	pending := pendingState != nil
	includeRoot := access.ImportExistingLocalContent
	if retainedImportState != nil {
		includeRoot = retainedImportState.IncludeRoot
	}
	if !access.ImportExistingLocalContent && !pending && retainedImportState == nil {
		return nil, nil
	}
	info, err := os.Stat(memoryDir)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("memory import: stat existing memory dir: %w", err)
	}
	if !info.IsDir() {
		return nil, fmt.Errorf("memory import: existing memory path is not a directory: %s", memoryDir)
	}
	if _, err := os.Stat(filepath.Join(memoryDir, ".git")); err == nil {
		return nil, nil
	} else if err != nil && !os.IsNotExist(err) {
		return nil, fmt.Errorf("memory import: stat existing memory .git: %w", err)
	}
	if _, err := vault.ReadRetentionState(memoryDir); err == nil {
		return nil, nil
	} else if err != nil && !os.IsNotExist(err) {
		return nil, fmt.Errorf("memory import: read retention state: %w", err)
	}
	if dirIsEmpty(memoryDir) {
		if err := os.Remove(memoryDir); err != nil {
			return nil, fmt.Errorf("memory import: remove empty existing memory dir: %w", err)
		}
		return nil, nil
	}

	stagingRoot := filepath.Join(workDir, ".memory-imports")
	if err := os.MkdirAll(stagingRoot, 0o755); err != nil {
		return nil, fmt.Errorf("memory import: mkdir staging dir: %w", err)
	}
	stamp := time.Now().UTC().Format("20060102T150405Z")
	stagedPath := filepath.Join(stagingRoot, "memory.import-"+stamp)
	for i := 1; ; i++ {
		if _, err := os.Stat(stagedPath); os.IsNotExist(err) {
			break
		}
		stagedPath = filepath.Join(stagingRoot, fmt.Sprintf("memory.import-%s-%d", stamp, i))
	}
	if err := os.Rename(memoryDir, stagedPath); err != nil {
		return nil, fmt.Errorf("memory import: stage existing memory dir: %w", err)
	}
	prepared := &preparedExistingLocalMemoryDir{
		originalPath: memoryDir,
		stagedPath:   stagedPath,
		pending:      pending,
		includeRoot:  includeRoot,
	}
	if err := writeExistingLocalMemoryImportState(stagedPath, existingLocalMemoryImportState{
		SchemaVersion: existingLocalMemoryImportSchema,
		MemoryID:      strings.TrimSpace(access.MemoryID),
		AgentID:       strings.TrimSpace(agentID),
		Branch:        strings.TrimSpace(access.Branch),
		IncludeRoot:   prepared.includeRoot,
	}); err != nil {
		restorePreparedExistingLocalMemoryDir(ctx, prepared, agentID)
		return nil, fmt.Errorf("memory import: write retained import state: %w", err)
	}
	slog.InfoContext(ctx, "memory import: staged existing local memory directory",
		"agent_id", agentID, "original", memoryDir, "staged", stagedPath)
	return prepared, nil
}

func findRetainedExistingLocalMemoryImport(ctx context.Context, workDir, agentID string, access MemoryAccess) (*preparedExistingLocalMemoryDir, error) {
	stagingRoot := filepath.Join(workDir, ".memory-imports")
	entries, err := os.ReadDir(stagingRoot)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("memory import: read retained imports: %w", err)
	}
	for _, entry := range entries {
		if !entry.IsDir() {
			continue
		}
		stagedPath := filepath.Join(stagingRoot, entry.Name())
		state, err := readExistingLocalMemoryImportState(stagedPath)
		if err != nil {
			slog.WarnContext(ctx, "memory import: ignoring retained import with invalid state",
				"agent_id", agentID, "path", stagedPath, "error", err)
			continue
		}
		if state == nil || !existingLocalMemoryImportStateMatches(*state, agentID, access) {
			continue
		}
		slog.InfoContext(ctx, "memory import: retrying retained local content",
			"agent_id", agentID, "memory_id", access.MemoryID, "path", stagedPath)
		pending, err := matchingPendingLocalMemoryRoot(stagedPath, agentID, access)
		if err != nil {
			return nil, fmt.Errorf("memory import: read retained pending state: %w", err)
		}
		return &preparedExistingLocalMemoryDir{
			originalPath: filepath.Join(workDir, "memory"),
			stagedPath:   stagedPath,
			pending:      pending,
			includeRoot:  state.IncludeRoot,
		}, nil
	}
	return nil, nil
}

func writeExistingLocalMemoryImportState(stagedPath string, state existingLocalMemoryImportState) error {
	existing, err := readExistingLocalMemoryImportState(stagedPath)
	if err != nil {
		return err
	}
	if existing != nil {
		if *existing == state {
			return nil
		}
		return fmt.Errorf("retained import state differs from existing marker")
	}
	data, err := json.MarshalIndent(state, "", "  ")
	if err != nil {
		return err
	}
	data = append(data, '\n')
	tmp, err := os.CreateTemp(stagedPath, ".agentteam-memory-import-*")
	if err != nil {
		return err
	}
	tmpPath := tmp.Name()
	defer os.Remove(tmpPath)
	if err := tmp.Chmod(0o600); err != nil {
		_ = tmp.Close()
		return err
	}
	if _, err := tmp.Write(data); err != nil {
		_ = tmp.Close()
		return err
	}
	if err := tmp.Sync(); err != nil {
		_ = tmp.Close()
		return err
	}
	if err := tmp.Close(); err != nil {
		return err
	}
	return os.Rename(tmpPath, filepath.Join(stagedPath, existingLocalMemoryImportMarker))
}

func readExistingLocalMemoryImportState(stagedPath string) (*existingLocalMemoryImportState, error) {
	data, err := os.ReadFile(filepath.Join(stagedPath, existingLocalMemoryImportMarker))
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	var state existingLocalMemoryImportState
	if err := json.Unmarshal(data, &state); err != nil {
		return nil, err
	}
	if state.SchemaVersion != existingLocalMemoryImportSchema || strings.TrimSpace(state.MemoryID) == "" ||
		strings.TrimSpace(state.AgentID) == "" || strings.TrimSpace(state.Branch) == "" {
		return nil, fmt.Errorf("invalid retained import binding state")
	}
	return &state, nil
}

func existingLocalMemoryImportStateMatches(state existingLocalMemoryImportState, agentID string, access MemoryAccess) bool {
	return strings.TrimSpace(state.MemoryID) == strings.TrimSpace(access.MemoryID) &&
		strings.TrimSpace(state.AgentID) == strings.TrimSpace(agentID) &&
		strings.TrimSpace(state.Branch) == strings.TrimSpace(access.Branch)
}

func restorePreparedExistingLocalMemoryDir(ctx context.Context, prepared *preparedExistingLocalMemoryDir, agentID string) {
	if prepared == nil || prepared.stagedPath == "" || prepared.originalPath == "" {
		return
	}
	if _, err := os.Stat(prepared.originalPath); err == nil {
		slog.WarnContext(ctx, "memory import: cannot restore staged memory dir because target exists",
			"agent_id", agentID, "staged", prepared.stagedPath, "target", prepared.originalPath)
		return
	}
	if err := os.Rename(prepared.stagedPath, prepared.originalPath); err != nil {
		slog.WarnContext(ctx, "memory import: restore staged memory dir failed",
			"agent_id", agentID, "staged", prepared.stagedPath, "target", prepared.originalPath, "error", err)
	}
}

// importExistingLocalMemoryIntoClone imports existing local MEMORY.md and a
// staged non-git memory/ directory into the managed Memory clone. Same-path
// remote content wins; conflicting local memory content is written under
// .conflicts inside the clone so the one import commit preserves both copies.
func importExistingLocalMemoryIntoClone(ctx context.Context, workDir string, prepared *preparedExistingLocalMemoryDir, clone *vault.Clone, agentID string, pushAuth gittransport.AuthMethod) *legacyUserPreferencesMigrationFinalization {
	legacyPreferencesMigrated, err := legacyUserPreferencesMigrationComplete(workDir, agentID, clone)
	if err != nil {
		slog.WarnContext(ctx, "memory import: read legacy user preference migration state failed",
			"agent_id", agentID, "error", err)
		return nil
	}
	legacyPreferencesReady, err := legacyUserPreferencesMigrationReady(workDir, agentID, clone)
	if err != nil {
		slog.WarnContext(ctx, "memory import: read prepared legacy user preference migration state failed",
			"agent_id", agentID, "error", err)
		return nil
	}
	if legacyPreferencesReady != nil && !legacyPreferencesMigrated &&
		legacyPreferencesReady.Phase == legacyUserPreferencesMigrationPhaseCutoverReady {
		return newLegacyUserPreferencesMigrationFinalization(
			workDir, agentID, clone, pushAuth, legacyPreferencesReady.GeneralImportedBytes,
		)
	}
	identityMigrated, err := migrations.MigrateIdentityDocuments(workDir)
	if err != nil {
		slog.WarnContext(ctx, "memory import: migrate root identity failed",
			"agent_id", agentID, "error", err)
		return nil
	}
	// The historical kb/user-preferences.md source remains writable until a
	// fallback Process is fully stopped. General existing-local-content import
	// runs now, but the legacy source is probed only by the cutover finalizer.
	sources, generalImportedBytes, overLimit, err := collectExistingLocalMemoryImportSources(workDir, prepared)
	if err != nil {
		slog.WarnContext(ctx, "memory import: collect existing local content failed",
			"agent_id", agentID, "error", err)
		return nil
	}
	if overLimit {
		backupOversizedExistingLocalMemory(ctx, workDir, prepared, agentID)
		return nil
	}
	if legacyPreferencesReady != nil && legacyPreferencesReady.GeneralImportedBytes > generalImportedBytes {
		// Never restore budget when a retry observes fewer source bytes.
		generalImportedBytes = legacyPreferencesReady.GeneralImportedBytes
	}
	if !legacyPreferencesMigrated {
		if err := markLegacyUserPreferencesGeneralImportPending(
			workDir, agentID, clone.MemoryID(), clone.Branch(), generalImportedBytes,
		); err != nil {
			slog.WarnContext(ctx, "memory import: persist general import budget failed",
				"agent_id", agentID, "error", err)
			return nil
		}
	}
	if len(sources) == 0 && !identityMigrated {
		if prepared == nil || !prepared.pending || prepared.includeRoot {
			removeEmptyExistingLocalMemoryRoot(ctx, workDir, agentID)
		}
		cleanupPreparedExistingLocalMemoryDir(ctx, prepared, agentID)
		if legacyPreferencesMigrated {
			return nil
		}
		return prepareLegacyUserPreferencesCutover(ctx, workDir, agentID, clone, pushAuth, generalImportedBytes)
	}

	stamp := time.Now().UTC().Format("20060102T150405Z")
	changed := identityMigrated
	allSourcesSafe := true
	for _, source := range sources {
		wrote, err := importExistingLocalMemorySource(clone.Path(), source, stamp)
		if err != nil {
			allSourcesSafe = false
			slog.WarnContext(ctx, "memory import: import local file failed",
				"agent_id", agentID, "path", source.rel, "error", err)
			continue
		}
		changed = changed || wrote
	}
	if !changed && !allSourcesSafe {
		return nil
	}

	if changed {
		commitMsg := fmt.Sprintf("chore: import existing local memory\n\nImported existing local memory content for agent %s.", agentID)
		if _, err := clone.CommitPending(ctx, commitMsg, vault.CommitPendingOpts{}); err != nil {
			if !errors.Is(err, vault.ErrNothingToCommit) {
				slog.WarnContext(ctx, "memory import: commit failed",
					"agent_id", agentID, "error", err)
				return nil
			}
		}
	}
	pushCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()
	if _, err := clone.Push(pushCtx, vault.PushOpts{Auth: pushAuth}); err != nil && !errors.Is(err, vault.ErrNothingToPush) {
		slog.WarnContext(ctx, "memory import: push failed",
			"agent_id", agentID, "error", err)
		return nil
	}
	if !allSourcesSafe {
		stagedPath := ""
		if prepared != nil {
			stagedPath = prepared.stagedPath
		}
		slog.WarnContext(ctx, "memory import: retaining local sources after partial import",
			"agent_id", agentID, "staged", stagedPath)
		return nil
	}
	var migration *legacyUserPreferencesMigrationFinalization
	if !legacyPreferencesMigrated {
		// Persist the cumulative budget before deleting any source.
		migration = prepareLegacyUserPreferencesCutover(ctx, workDir, agentID, clone, pushAuth, generalImportedBytes)
		if migration == nil {
			return nil
		}
	}
	removeImportedLocalSources(ctx, workDir, prepared, sources, agentID)
	slog.InfoContext(ctx, "memory import: completed and pushed existing local content",
		"agent_id", agentID)
	return migration
}

func isolatePendingLocalMemoryRoot(workDir string) (string, error) {
	memoryDir := filepath.Join(workDir, "memory")
	backupDir := filepath.Join(workDir, ".memory-pending-backups")
	if err := os.MkdirAll(backupDir, 0o700); err != nil {
		return "", err
	}
	stamp := time.Now().UTC().Format("20060102T150405Z")
	isolatedPath := uniquePath(filepath.Join(backupDir, "memory.pending-"+stamp))
	if err := os.Rename(memoryDir, isolatedPath); err != nil {
		return "", err
	}
	return isolatedPath, nil
}

func collectExistingLocalMemoryImportSources(workDir string, prepared *preparedExistingLocalMemoryDir) ([]existingLocalMemoryImportSource, int64, bool, error) {
	var sources []existingLocalMemoryImportSource
	var total int64

	if prepared == nil || !prepared.pending || prepared.includeRoot {
		rootMemoryPath := filepath.Join(workDir, "MEMORY.md")
		if content, ok, overLimit, err := readImportSource(rootMemoryPath, existingLocalMemoryImportMaxBytes-total); err != nil {
			return nil, total, false, err
		} else if overLimit {
			return sources, total, true, nil
		} else {
			total += int64(len(content))
			if ok {
				sources = append(sources, existingLocalMemoryImportSource{
					rel:               "MEMORY.md",
					filePath:          rootMemoryPath,
					content:           content,
					removeAfterImport: true,
				})
			}
		}
	}

	if prepared == nil || prepared.stagedPath == "" {
		return sources, total, false, nil
	}
	overLimit := false
	err := filepath.WalkDir(prepared.stagedPath, func(filePath string, entry fs.DirEntry, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}
		if entry.IsDir() {
			if entry.Name() == ".git" {
				return filepath.SkipDir
			}
			return nil
		}
		if entry.Type()&os.ModeType != 0 {
			return nil
		}
		rel, err := filepath.Rel(prepared.stagedPath, filePath)
		if err != nil {
			return err
		}
		rel = filepath.ToSlash(filepath.Clean(rel))
		if rel == "." || rel == ".git" || strings.HasPrefix(rel, ".git/") {
			return nil
		}
		if rel == pendingLocalMemoryMarker || rel == existingLocalMemoryImportMarker ||
			strings.HasPrefix(rel, ".agentteam-pending-memory-") || strings.HasPrefix(rel, ".agentteam-memory-import-") {
			return nil
		}
		content, ok, fileOverLimit, err := readImportSource(filePath, existingLocalMemoryImportMaxBytes-total)
		if err != nil {
			return err
		}
		if fileOverLimit {
			overLimit = true
			return fs.SkipAll
		}
		total += int64(len(content))
		if !ok {
			return nil
		}
		if prepared.pending && rel == "MEMORY.md" && bytes.Equal(content, []byte(pendingLocalMemoryIndex)) {
			return nil
		}
		sources = append(sources, existingLocalMemoryImportSource{
			rel:      rel,
			filePath: filePath,
			content:  content,
		})
		return nil
	})
	if err != nil {
		return nil, total, false, err
	}
	return sources, total, overLimit, nil
}

func readImportSource(filePath string, maxBytes int64) (content []byte, importable, overLimit bool, err error) {
	info, err := os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, false, false, nil
		}
		return nil, false, false, fmt.Errorf("stat %s: %w", filePath, err)
	}
	if info.IsDir() || info.Mode()&os.ModeType != 0 {
		return nil, false, false, nil
	}
	if maxBytes < 0 || info.Size() > maxBytes {
		return nil, false, true, nil
	}
	file, err := os.Open(filePath)
	if err != nil {
		return nil, false, false, fmt.Errorf("open %s: %w", filePath, err)
	}
	defer file.Close()
	content, err = io.ReadAll(io.LimitReader(file, maxBytes+1))
	if err != nil {
		return nil, false, false, fmt.Errorf("read %s: %w", filePath, err)
	}
	if int64(len(content)) > maxBytes {
		return nil, false, true, nil
	}
	if len(bytes.TrimSpace(content)) == 0 {
		return content, false, false, nil
	}
	return content, true, false, nil
}

func importExistingLocalMemorySource(clonePath string, source existingLocalMemoryImportSource, stamp string) (bool, error) {
	rel := path.Clean(source.rel)
	if rel == "." || strings.HasPrefix(rel, "../") || path.IsAbs(rel) {
		return false, fmt.Errorf("unsafe relative path %q", source.rel)
	}
	destPath := filepath.Join(clonePath, filepath.FromSlash(rel))
	if exists, equal, nonEmpty, err := inspectExistingImportDestination(destPath, source.content); err == nil && exists {
		if equal {
			return false, nil
		}
		if nonEmpty {
			if source.skipIfDestNonEmpty {
				return false, nil
			}
			return writeMemoryImportConflict(clonePath, rel, source.content, stamp)
		}
	} else if err != nil && !os.IsNotExist(err) {
		if source.skipIfDestNonEmpty {
			return false, nil
		}
		return writeMemoryImportConflict(clonePath, rel, source.content, stamp)
	}
	if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
		if wrote, conflictErr := writeMemoryImportConflict(clonePath, rel, source.content, stamp); conflictErr == nil {
			return wrote, nil
		}
		return false, err
	}
	if err := os.WriteFile(destPath, source.content, 0o644); err != nil {
		if wrote, conflictErr := writeMemoryImportConflict(clonePath, rel, source.content, stamp); conflictErr == nil {
			return wrote, nil
		}
		return false, err
	}
	return true, nil
}

func mergeLegacyUserPreferences(destPath string, source []byte) (bool, error) {
	sourceText := strings.TrimSpace(string(source))
	if sourceText == "" {
		return false, nil
	}
	body := sourceText
	if lines := strings.Split(sourceText, "\n"); len(lines) > 0 && strings.HasPrefix(strings.TrimSpace(lines[0]), "# ") {
		body = strings.TrimSpace(strings.Join(lines[1:], "\n"))
	}
	if body == "" {
		return false, nil
	}
	section := "## Imported User Preferences\n\n" + body

	existing, err := os.ReadFile(destPath)
	if err != nil && !os.IsNotExist(err) {
		return false, err
	}
	if os.IsNotExist(err) || len(bytes.TrimSpace(existing)) == 0 {
		if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
			return false, err
		}
		return true, os.WriteFile(destPath, []byte("# User Profile\n\n"+section+"\n"), 0o644)
	}
	if strings.Contains(string(existing), section) || strings.Contains(string(existing), sourceText) {
		return false, nil
	}
	merged := strings.TrimRight(string(existing), "\n") + "\n\n" + section + "\n"
	return true, os.WriteFile(destPath, []byte(merged), 0o644)
}

func inspectExistingImportDestination(filePath string, content []byte) (exists, equal, nonEmpty bool, err error) {
	info, err := os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return false, false, false, nil
		}
		return false, false, false, err
	}
	if !info.Mode().IsRegular() {
		return true, false, true, fmt.Errorf("destination is not a regular file")
	}
	if info.Size() != int64(len(content)) {
		return true, false, info.Size() > 0, nil
	}
	file, err := os.Open(filePath)
	if err != nil {
		return true, false, true, err
	}
	defer file.Close()
	existing, err := io.ReadAll(io.LimitReader(file, int64(len(content))+1))
	if err != nil {
		return true, false, true, err
	}
	return true, bytes.Equal(existing, content), len(bytes.TrimSpace(existing)) > 0, nil
}

func writeMemoryImportConflict(clonePath, rel string, content []byte, stamp string) (bool, error) {
	conflictRel := path.Join(".conflicts", rel+".conflict-"+stamp)
	conflictPath := uniquePath(filepath.Join(clonePath, filepath.FromSlash(conflictRel)))
	if err := os.MkdirAll(filepath.Dir(conflictPath), 0o755); err != nil {
		return false, err
	}
	if err := os.WriteFile(conflictPath, content, 0o644); err != nil {
		return false, err
	}
	return true, nil
}

func removeImportedLocalSources(ctx context.Context, workDir string, prepared *preparedExistingLocalMemoryDir, sources []existingLocalMemoryImportSource, agentID string) {
	for _, source := range sources {
		if !source.removeAfterImport {
			continue
		}
		if err := os.Remove(source.filePath); err != nil && !os.IsNotExist(err) {
			slog.WarnContext(ctx, "memory import: remove existing local memory source failed",
				"agent_id", agentID, "path", source.filePath, "error", err)
		}
	}
	cleanupPreparedExistingLocalMemoryDir(ctx, prepared, agentID)
}

func removeEmptyExistingLocalMemoryRoot(ctx context.Context, workDir, agentID string) {
	rootMemoryPath := filepath.Join(workDir, "MEMORY.md")
	_, nonEmpty, overLimit, err := readImportSource(rootMemoryPath, 4<<10)
	if err != nil {
		slog.WarnContext(ctx, "memory import: read existing local MEMORY.md failed",
			"agent_id", agentID, "error", err)
		return
	}
	if nonEmpty || overLimit {
		return
	}
	if err := os.Remove(rootMemoryPath); err != nil && !os.IsNotExist(err) {
		slog.WarnContext(ctx, "memory import: remove empty existing local MEMORY.md failed",
			"agent_id", agentID, "error", err)
	}
}

func cleanupPreparedExistingLocalMemoryDir(ctx context.Context, prepared *preparedExistingLocalMemoryDir, agentID string) {
	if prepared == nil || prepared.stagedPath == "" {
		return
	}
	if err := os.RemoveAll(prepared.stagedPath); err != nil {
		slog.WarnContext(ctx, "memory import: remove staged memory dir failed",
			"agent_id", agentID, "path", prepared.stagedPath, "error", err)
	}
	removeEmptyDir(filepath.Dir(prepared.stagedPath))
}

func backupOversizedExistingLocalMemory(ctx context.Context, workDir string, prepared *preparedExistingLocalMemoryDir, agentID string) {
	backupDir := filepath.Join(workDir, ".memory-backups")
	if err := os.MkdirAll(backupDir, 0o755); err != nil {
		slog.WarnContext(ctx, "memory import: mkdir backup dir failed",
			"agent_id", agentID, "error", err)
		return
	}
	stamp := time.Now().UTC().Format("20060102T150405Z")
	includeRoot := prepared == nil || !prepared.pending || prepared.includeRoot
	rootPath := filepath.Join(workDir, "MEMORY.md")
	if includeRoot {
		if info, err := os.Stat(rootPath); err == nil && info.Mode().IsRegular() {
			backupPath := uniquePath(filepath.Join(backupDir, "MEMORY.backup-"+stamp+".md"))
			if err := os.Rename(rootPath, backupPath); err != nil {
				slog.WarnContext(ctx, "memory import: move oversized root backup failed",
					"agent_id", agentID, "path", rootPath, "error", err)
				return
			}
		} else if err != nil && !os.IsNotExist(err) {
			slog.WarnContext(ctx, "memory import: stat oversized root backup failed",
				"agent_id", agentID, "path", rootPath, "error", err)
			return
		}
	}
	if prepared != nil && prepared.stagedPath != "" {
		backupPath := uniquePath(filepath.Join(backupDir, "memory.backup-"+stamp))
		if err := os.Rename(prepared.stagedPath, backupPath); err != nil {
			slog.WarnContext(ctx, "memory import: backup oversized memory dir failed",
				"agent_id", agentID, "error", err)
			return
		}
		removeEmptyDir(filepath.Dir(prepared.stagedPath))
	}
	slog.WarnContext(ctx, "memory import: existing local content exceeds import limit; backed up without importing",
		"agent_id", agentID, "max_bytes", existingLocalMemoryImportMaxBytes)
}

func dirIsEmpty(dir string) bool {
	entries, err := os.ReadDir(dir)
	return err == nil && len(entries) == 0
}

func removeEmptyDir(dir string) {
	if dirIsEmpty(dir) {
		_ = os.Remove(dir)
	}
}

func uniquePath(base string) string {
	if _, err := os.Stat(base); err != nil {
		return base
	}
	for i := 1; ; i++ {
		candidate := fmt.Sprintf("%s-%d", base, i)
		if _, err := os.Stat(candidate); err != nil {
			return candidate
		}
	}
}
