package memory

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"regexp"
	"strings"
	"time"

	"gorm.io/gorm"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/service/resource/activity"
	reposservice "github.com/tidbcloud/AgentTeam/internal/service/resource/repos"
	activitystore "github.com/tidbcloud/AgentTeam/internal/store/resource/activity"
	memorystore "github.com/tidbcloud/AgentTeam/internal/store/resource/memory"
	reposstore "github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
	"github.com/tidbcloud/AgentTeam/presets"
)

type MemoryCreateMutation struct {
	Name             string
	RepoNameOverride string
	Description      string
	StorageScope     string
	IsActive         *bool
	TemplateLocale   string
	Actor            reposservice.ResourceRepositoryActor
}

type MemoryUpdateMutation struct {
	Name        string
	Slug        string
	Description string
	IsActive    *bool
}

type MemoryForkMutation struct {
	Name             string
	RepoNameOverride string
	Description      string
	StorageScope     string
	Actor            reposservice.ResourceRepositoryActor
}

type MemoryPermissionGrantMutation struct {
	GrantID     string
	SubjectType string
	SubjectID   string
	Role        string
}

type AgentMemoryBindingMutation struct {
	MemoryID                    string
	Branch                      string
	ImportExistingLocalContent  bool
	ReplaceLocalContentOnSwitch bool
}

type AgentMemoryBindingResult struct {
	AgentID                     string
	MemoryID                    string
	PreviousMemoryID            string
	Branch                      string
	ImportExistingLocalContent  bool
	ReplaceLocalContentOnSwitch bool
	Unbound                     bool
}

type AgentOwnership struct {
	ID          string
	Name        string
	DisplayName string
}

type AgentMemoryBindingSummary struct {
	ID                          string
	AgentID                     string
	MemoryID                    string
	Branch                      string
	ImportExistingLocalContent  bool
	ReplaceLocalContentOnSwitch bool
	CreatedBy                   string
	UpdatedBy                   string
	CreatedAt                   time.Time
	UpdatedAt                   time.Time
}

type AgentMemoryBindingMemory struct {
	ID                          string
	Slug                        string
	Name                        string
	Description                 string
	IsActive                    bool
	Role                        string
	CanManage                   bool
	CanBind                     bool
	Repository                  *reposstore.ResourceRepository
	BoundAgentID                string
	BoundAgentName              string
	BoundBranch                 string
	ImportExistingLocalContent  bool
	ReplaceLocalContentOnSwitch bool
	UpdatedAt                   time.Time
}

type AgentMemoryBindingsSummary struct {
	OwnedAgents []AgentOwnership
	Bindings    []AgentMemoryBindingSummary
	Memories    []AgentMemoryBindingMemory
}

type MemoryPermissionGrant struct {
	ID          string
	MemoryID    string
	SubjectType string
	SubjectID   string
	Role        string
	CreatedBy   string
	UpdatedBy   string
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

type MemoryPermissionsSummary struct {
	MyRole    string
	CanManage bool
	Owner     *MemoryPermissionGrant
	Members   []MemoryPermissionGrant
}

type AgentAccessibleMemory struct {
	ID                          string
	Name                        string
	Description                 string
	AccessLevel                 string
	PermissionSetAt             time.Time
	UpdatedAt                   time.Time
	LastSyncAt                  time.Time
	Repository                  *reposstore.ResourceRepository
	Branch                      string
	ImportExistingLocalContent  bool
	ReplaceLocalContentOnSwitch bool
	// SyncProtocol is selected by the server from the persisted exact-binding
	// authority. Clients must execute only this protocol for the lifetime of
	// the returned projection and must never fall back after a protocol error.
	SyncProtocol         string
	ContentID            string
	ContentVersionID     string
	ContentGeneration    uint64
	AcknowledgedSequence uint64
	ActiveReplicaID      string
}

type AgentCloudMemory struct {
	AgentID                    string
	Enabled                    bool
	MemoryID                   string
	Name                       string
	Branch                     string
	Repository                 *reposstore.ResourceRepository
	Status                     string
	SyncStatus                 string
	LastSyncAt                 time.Time
	LastBackupAt               time.Time
	ImportExistingLocalContent bool
}

type AgentCloudMemoryEnableMutation struct {
	ImportExistingLocalContent *bool
	TemplateLocale             string
	Actor                      reposservice.ResourceRepositoryActor
}

type AgentCloudMemoryBackup struct {
	TagName   string
	Name      string
	CommitSHA string
	CreatedAt time.Time
}

type AgentCloudMemoryBackupPage struct {
	Items   []AgentCloudMemoryBackup
	Page    int
	PerPage int
	HasMore bool
}

type AgentCloudMemoryBackupCreateMutation struct {
	Name  string
	Actor reposservice.ResourceRepositoryActor
}

type AgentMemoryAccessBatch struct {
	Agents           map[string]*AgentAccessibleMemory
	SyncToken        string
	SyncTokenExpires time.Time
}

type MemoryCollectionPage struct {
	Items   []memorystore.Memory
	Total   int64
	Page    int
	PerPage int
	HasMore bool
}

type MemoryRepositories struct {
	Primary *reposstore.ResourceRepository
}

type MemoryServicer interface {
	CreateMemory(ctx context.Context, workspaceID, subject string, input MemoryCreateMutation) (memorystore.Memory, error)
	ForkMemory(ctx context.Context, workspaceID, subject, id string, input MemoryForkMutation) (memorystore.Memory, error)
	GetMemory(ctx context.Context, workspaceID, subject, id string) (memorystore.Memory, error)
	GetMemoryRepositories(ctx context.Context, workspaceID, subject, id string) (MemoryRepositories, error)
	ListMemories(ctx context.Context, workspaceID, subject, view, query string, page, perPage int) (MemoryCollectionPage, error)
	UpdateMemory(ctx context.Context, workspaceID, subject, id string, input MemoryUpdateMutation) (memorystore.Memory, error)
	DeleteMemory(ctx context.Context, workspaceID, subject, id string) error
	PinMemory(ctx context.Context, workspaceID, subject, id string) error
	UnpinMemory(ctx context.Context, workspaceID, subject, id string) error
	GetAgentAccessibleMemory(ctx context.Context, workspaceID, subject, agentID string) (*AgentAccessibleMemory, error)
	BatchListAgentAccessibleMemories(ctx context.Context, workspaceID, subject string, agentIDs []string) (AgentMemoryAccessBatch, error)
	GetAgentCloudMemory(ctx context.Context, workspaceID, subject, agentID string) (AgentCloudMemory, error)
	EnableAgentCloudMemory(ctx context.Context, workspaceID, subject, agentID string, input AgentCloudMemoryEnableMutation) (AgentCloudMemory, error)
	DisableAgentCloudMemory(ctx context.Context, workspaceID, subject, agentID string) (AgentCloudMemory, error)
	ListAgentCloudMemoryCommits(ctx context.Context, workspaceID, subject, agentID string, opts AgentCloudMemoryCommitListOptions) (json.RawMessage, error)
	GetAgentCloudMemoryCompare(ctx context.Context, workspaceID, subject, agentID string, opts AgentCloudMemoryCompareOptions) (json.RawMessage, error)
	ListAgentCloudMemoryBackups(ctx context.Context, workspaceID, subject, agentID, query string, page, perPage int) (AgentCloudMemoryBackupPage, error)
	CreateAgentCloudMemoryBackup(ctx context.Context, workspaceID, subject, agentID string, input AgentCloudMemoryBackupCreateMutation) (AgentCloudMemoryBackup, error)
	RestoreAgentCloudMemoryBackup(ctx context.Context, workspaceID, subject, agentID, tagName string) (AgentCloudMemoryBackup, error)
	BindAgentMemory(ctx context.Context, workspaceID, subject, agentID string, input AgentMemoryBindingMutation) (AgentMemoryBindingResult, error)
	UnbindAgentMemory(ctx context.Context, workspaceID, subject, agentID string) (AgentMemoryBindingResult, error)
	ListAgentMemoryBindings(ctx context.Context, workspaceID, subject string) (AgentMemoryBindingsSummary, error)
	ListMemoryPermissions(ctx context.Context, workspaceID, subject, memoryID string) (MemoryPermissionsSummary, error)
	UpsertMemoryPermission(ctx context.Context, workspaceID, subject, memoryID string, input MemoryPermissionGrantMutation) (MemoryPermissionGrant, error)
	DeleteMemoryPermission(ctx context.Context, workspaceID, subject, memoryID, grantID string) error
}

type AgentOwnershipSource interface {
	ListOwnedAgents(ctx context.Context, workspaceID, subject string) ([]AgentOwnership, error)
}

type MemoryServiceOptions struct {
	RepositoryInitializer MemoryRepositoryInitializer
	RepositoryForker      MemoryRepositoryForker
	RepositoryDeleter     reposservice.RepositoryDeleter
	RepositoryBackups     MemoryRepositoryBackupStore
	RepositoryHistory     MemoryRepositoryHistoryStore
	RepositoryGrantor     MemoryRepositoryAccessGrantor
	AgentOwnershipSource  AgentOwnershipSource
	SyncTokenIssuer       reposservice.RepositorySyncTokenIssuer
	ActivityStore         *activitystore.Store
	ActivityProjector     activity.ResourceActivityProjectorRefresher
	ActivityRefreshAfter  time.Duration
	ContentAuthority      ContentAuthorityReader
	NativeContent         NativeMemoryContent
	NativeContentEnabled  bool
	MigrationSource       MemoryMigrationSource
	MigrationWorker       MemoryContentMigrationWorker
	MigrationMappings     MemoryMigrationMappingReader
	MigrationContent      MemoryMigrationContent
}

type MemoryService struct {
	store                 *memorystore.Store
	repositoryStore       *reposstore.Store
	repositories          reposservice.RepositoryEnsurer
	repositoryInitializer MemoryRepositoryInitializer
	repositoryForker      MemoryRepositoryForker
	repositoryDeleter     reposservice.RepositoryDeleter
	repositoryBackups     MemoryRepositoryBackupStore
	repositoryHistory     MemoryRepositoryHistoryStore
	repositoryGrantor     MemoryRepositoryAccessGrantor
	agentOwnershipSource  AgentOwnershipSource
	syncTokenIssuer       reposservice.RepositorySyncTokenIssuer
	activityStore         *activitystore.Store
	activityProjector     activity.ResourceActivityProjectorRefresher
	activityRefreshAfter  time.Duration
	contentAuthority      ContentAuthorityReader
	nativeContent         NativeMemoryContent
	nativeContentEnabled  bool
	migrationSource       MemoryMigrationSource
	migrationWorker       MemoryContentMigrationWorker
	migrationMappings     MemoryMigrationMappingReader
	migrationContent      MemoryMigrationContent
	now                   func() time.Time
}

func NewMemoryService(store *memorystore.Store, repositoryStore *reposstore.Store, repositories reposservice.RepositoryEnsurer, now func() time.Time) *MemoryService {
	return NewMemoryServiceWithOptions(store, repositoryStore, repositories, now, MemoryServiceOptions{})
}

func NewMemoryServiceWithOptions(store *memorystore.Store, repositoryStore *reposstore.Store, repositories reposservice.RepositoryEnsurer, now func() time.Time, options MemoryServiceOptions) *MemoryService {
	if now == nil {
		now = func() time.Time { return time.Now().UTC() }
	}
	initializer := options.RepositoryInitializer
	if initializer == nil {
		if resolved, ok := repositories.(MemoryRepositoryInitializer); ok {
			initializer = resolved
		}
	}
	forker := options.RepositoryForker
	if forker == nil {
		if resolved, ok := repositories.(MemoryRepositoryForker); ok {
			forker = resolved
		}
	}
	deleter := options.RepositoryDeleter
	if deleter == nil {
		if resolved, ok := repositories.(reposservice.RepositoryDeleter); ok {
			deleter = resolved
		}
	}
	backups := options.RepositoryBackups
	if backups == nil {
		if resolved, ok := repositories.(MemoryRepositoryBackupStore); ok {
			backups = resolved
		}
	}
	history := options.RepositoryHistory
	if history == nil {
		if resolved, ok := repositories.(MemoryRepositoryHistoryStore); ok {
			history = resolved
		}
	}
	if history == nil {
		if resolved, ok := initializer.(MemoryRepositoryHistoryStore); ok {
			history = resolved
		}
	}
	if history == nil {
		if resolved, ok := backups.(MemoryRepositoryHistoryStore); ok {
			history = resolved
		}
	}
	grantor := options.RepositoryGrantor
	if grantor == nil {
		if resolved, ok := repositories.(MemoryRepositoryAccessGrantor); ok {
			grantor = resolved
		}
	}
	if grantor == nil {
		if resolved, ok := initializer.(MemoryRepositoryAccessGrantor); ok {
			grantor = resolved
		}
	}
	if grantor == nil {
		if resolved, ok := backups.(MemoryRepositoryAccessGrantor); ok {
			grantor = resolved
		}
	}
	return &MemoryService{
		store: store, repositoryStore: repositoryStore, repositories: repositories,
		repositoryInitializer: initializer, repositoryForker: forker, repositoryDeleter: deleter,
		repositoryBackups: backups, repositoryHistory: history, repositoryGrantor: grantor,
		agentOwnershipSource: options.AgentOwnershipSource, syncTokenIssuer: options.SyncTokenIssuer,
		activityStore: options.ActivityStore, activityProjector: options.ActivityProjector,
		activityRefreshAfter: activity.NormalizeResourceActivityRefreshAfter(options.ActivityRefreshAfter),
		contentAuthority:     options.ContentAuthority, nativeContent: options.NativeContent,
		nativeContentEnabled: options.NativeContentEnabled,
		migrationSource:      options.MigrationSource, migrationWorker: options.MigrationWorker,
		migrationMappings: options.MigrationMappings, migrationContent: options.MigrationContent,
		now: now,
	}
}

func (s *MemoryService) CreateMemory(ctx context.Context, workspaceID, subject string, input MemoryCreateMutation) (memorystore.Memory, error) {
	if s.store == nil {
		return memorystore.Memory{}, invalidArgument("memory service is not configured")
	}
	if err := validateSubject(subject); err != nil {
		return memorystore.Memory{}, err
	}
	name := strings.TrimSpace(input.Name)
	if name == "" {
		return memorystore.Memory{}, invalidArgument("memory name is required")
	}
	now := s.now()
	workspace := strings.TrimSpace(workspaceID)
	slug, field, err := reposservice.ResolveRepositoryNameStem(name, input.RepoNameOverride, "memory", ".mem", "memory")
	if err != nil {
		return memorystore.Memory{}, err
	}
	storage, err := reposservice.ResolveRepositoryStorage(workspace, subject, input.StorageScope)
	if err != nil {
		return memorystore.Memory{}, err
	}
	repoName := memoryRepositoryName(slug)
	repoFullName := reposservice.RepositoryFullName(storage.OwnerLogin, repoName)
	if s.repositories == nil {
		return memorystore.Memory{}, invalidArgument("memory repository provisioner is not configured")
	}
	if s.repositoryStore == nil {
		return memorystore.Memory{}, invalidArgument("resource repository store is not configured")
	}
	if s.repositoryInitializer == nil {
		return memorystore.Memory{}, invalidArgument("memory repository initializer is not configured")
	}
	m := memorystore.Memory{
		ID:                newMemoryID(),
		Slug:              slug,
		Name:              name,
		Description:       strings.TrimSpace(input.Description),
		StorageScope:      storage.Scope,
		StorageOwnerLogin: storage.OwnerLogin,
		IsActive:          true,
		CreatedBy:         strings.TrimSpace(subject),
		UpdatedBy:         strings.TrimSpace(subject),
		CreatedAt:         now,
		UpdatedAt:         now,
	}
	if input.IsActive != nil {
		m.IsActive = *input.IsActive
	}

	suggestedRepoName, err := reposservice.FirstAvailableRepositoryNameStem(ctx, s.repositoryStore, storage.OwnerLogin, slug, nil, ".mem")
	if err != nil {
		return memorystore.Memory{}, err
	}
	if err := reposservice.EnsureRepositoryFullNameAvailableForField(ctx, s.repositoryStore, repoFullName, reposstore.ResourceTypeMemory, m.ID, reposstore.RepoRolePrimary, field, suggestedRepoName); err != nil {
		return memorystore.Memory{}, err
	}
	repoResult, err := s.repositories.EnsureRepository(ctx, memoryRepositoryEnsureRequest(workspace, m.ID, storage.OwnerLogin, repoName, name, field, suggestedRepoName, "main", input.Actor))
	if err != nil {
		slog.ErrorContext(ctx, "memory repository provisioning failed",
			"stage", "ensure_repository",
			"workspace_id", workspace,
			"memory_id", m.ID,
			"memory_name", m.Name,
			"repo_owner", storage.OwnerLogin,
			"repo_name", repoName,
			"default_branch", "main",
			"error", err,
		)
		return memorystore.Memory{}, fmt.Errorf("ensure memory repository: %w", err)
	}

	locale := presets.NormalizeLocale(input.TemplateLocale)
	authorName, authorEmail := reposservice.RepositoryCommitAuthorFromActor(input.Actor, "MeshX Memory", "meshx-memory@agentteam.local")
	logMemoryRepositoryInitializationRequested(ctx, workspace, m, repoResult, locale)
	if err := s.repositoryInitializer.InitializeMemoryRepository(ctx, MemoryRepositoryInitializeRequest{
		Repository:  repoResult,
		Locale:      locale,
		Message:     presets.MemoryInitialCommitMessage(locale),
		AuthorName:  authorName,
		AuthorEmail: authorEmail,
	}); err != nil {
		slog.ErrorContext(ctx, "memory repository initialization failed",
			"stage", "initialize_repository",
			"workspace_id", workspace,
			"memory_id", m.ID,
			"memory_name", m.Name,
			"repo_owner", repoResult.Owner,
			"repo_name", repoResult.Name,
			"repo_full_name", repoResult.FullName,
			"default_branch", firstNonEmptyString(repoResult.DefaultBranch, "main"),
			"locale", locale,
			"error", err,
		)
		failureRepos := []reposstore.ResourceRepository{reposservice.ResourceRepositoryFailureRecord(workspace, reposstore.ResourceTypeMemory, m.ID, reposstore.RepoRolePrimary, storage, repoResult, subject, now)}
		_ = reposservice.RecordResourceRepositoryLifecycleFailure(ctx, s.repositoryStore, failureRepos, reposstore.ResourceRepositoryLifecycleOperationCreateBind, subject, now, err)
		s.recordMemoryRepoLifecycleFailedActivity(ctx, failureRepos, subject, now, err)
		return memorystore.Memory{}, fmt.Errorf("initialize memory repository: %w", err)
	}

	created, err := s.persistOwnedMemory(ctx, workspace, subject, m, repoResult, storage, firstNonEmptyString(repoResult.DefaultBranch, "main"), now, memoryActivityCreated, map[string]any{"name": m.Name, "slug": m.Slug})
	if err != nil {
		failureRepos := []reposstore.ResourceRepository{reposservice.ResourceRepositoryFailureRecord(workspace, reposstore.ResourceTypeMemory, m.ID, reposstore.RepoRolePrimary, storage, repoResult, subject, now)}
		_ = reposservice.RecordResourceRepositoryLifecycleFailure(ctx, s.repositoryStore, failureRepos, reposstore.ResourceRepositoryLifecycleOperationCreateBind, subject, now, err)
		s.recordMemoryRepoLifecycleFailedActivity(ctx, failureRepos, subject, now, err)
		return memorystore.Memory{}, err
	}
	return created, nil
}

func memoryRepositoryEnsureRequest(workspace, memoryID, owner, repoName, memoryName, conflictField, suggestedRepoName, defaultBranch string, actor reposservice.ResourceRepositoryActor) reposservice.AGSRepositoryEnsureRequest {
	return reposservice.AGSRepositoryEnsureRequest{
		WorkspaceID:       workspace,
		ResourceType:      reposstore.ResourceTypeMemory,
		ResourceID:        memoryID,
		RepoRole:          reposstore.RepoRolePrimary,
		Owner:             owner,
		Name:              repoName,
		Description:       "AgentTeam Memory repository for " + memoryName,
		Kind:              "memory_canonical",
		Visibility:        "private",
		UserVisible:       false,
		AutoInit:          false,
		RejectExisting:    true,
		NameConflictField: conflictField,
		SuggestedRepoName: suggestedRepoName,
		Features:          reposservice.AGSRepositoryFeatures{Issues: true, Wiki: true},
		DefaultBranch:     defaultBranch,
		Actor:             actor,
	}
}

func logMemoryRepositoryInitializationRequested(ctx context.Context, workspace string, m memorystore.Memory, repo reposservice.RepositoryIdentity, locale string) {
	slog.InfoContext(ctx, "memory repository initialization requested",
		"stage", "initialize_repository",
		"workspace_id", workspace,
		"memory_id", m.ID,
		"memory_name", m.Name,
		"repo_owner", repo.Owner,
		"repo_name", repo.Name,
		"repo_full_name", repo.FullName,
		"default_branch", firstNonEmptyString(repo.DefaultBranch, "main"),
		"locale", locale,
	)
}

func (s *MemoryService) persistOwnedMemory(ctx context.Context, workspaceID, subject string, m memorystore.Memory, repoResult reposservice.RepositoryIdentity, storage reposservice.RepositoryStorage, defaultRef string, now time.Time, eventType string, details map[string]any) (memorystore.Memory, error) {
	workspaceID = strings.TrimSpace(workspaceID)
	subject = strings.TrimSpace(subject)
	var created memorystore.Memory
	err := s.store.Transaction(ctx, func(tx *memorystore.Store) error {
		var err error
		created, err = tx.CreateMemory(ctx, m)
		if err != nil {
			return err
		}
		_, err = tx.UpsertMemoryGrant(ctx, memorystore.MemoryGrant{
			ID:          newMemoryID(),
			WorkspaceID: workspaceID,
			MemoryID:    created.ID,
			SubjectType: "user",
			SubjectID:   subject,
			Role:        "owner",
			CreatedBy:   subject,
			UpdatedBy:   subject,
			CreatedAt:   now,
			UpdatedAt:   now,
		})
		if err != nil {
			return err
		}
		repositoryTx := s.repositoryStore.WithDB(tx.DB())
		repoUpsert, err := repositoryTx.Upsert(ctx, reposservice.NewResourceRepository(reposservice.MappingInput{
			ID:           newMemoryID(),
			WorkspaceID:  workspaceID,
			ResourceType: reposstore.ResourceTypeMemory,
			ResourceID:   created.ID,
			RepoRole:     reposstore.RepoRolePrimary,
			OwnerKind:    storage.OwnerKind,
			Repository: reposservice.EnsuredRepository{
				AGSRepoID:     repoResult.ID,
				Owner:         repoResult.Owner,
				Name:          repoResult.Name,
				FullName:      repoResult.FullName,
				DefaultBranch: repoResult.DefaultBranch,
				Visibility:    "private",
				UserVisible:   false,
			},
			DefaultRef:  defaultRef,
			Visibility:  "private",
			UserVisible: false,
			ActorID:     subject,
			Now:         now,
		}))
		if err != nil {
			return err
		}
		if repoUpsert.Created {
			if err := activity.WriteResourceRepositoryLifecycleActivity(ctx, s.memoryActivityStore(tx), repoUpsert.Repository, activity.ResourceRepositoryActivityCreated, subject, now); err != nil {
				return err
			}
		}
		return writeMemoryActivity(ctx, s.memoryActivityStore(tx), workspaceID, created, repoResult.FullName, eventType, subject, "resource", created.ID, details, now)
	})
	if err != nil {
		return memorystore.Memory{}, err
	}
	return created, nil
}

func (s *MemoryService) ForkMemory(ctx context.Context, workspaceID, subject, id string, input MemoryForkMutation) (memorystore.Memory, error) {
	if s.store == nil {
		return memorystore.Memory{}, invalidArgument("memory service is not configured")
	}
	if err := validateSubject(subject); err != nil {
		return memorystore.Memory{}, err
	}
	workspace := strings.TrimSpace(workspaceID)
	subject = strings.TrimSpace(subject)
	source, err := s.GetMemory(ctx, workspace, subject, id)
	if err != nil {
		return memorystore.Memory{}, err
	}
	sourceRepos, err := s.GetMemoryRepositories(ctx, workspace, subject, source.ID)
	if err != nil {
		return memorystore.Memory{}, err
	}
	if sourceRepos.Primary == nil {
		return memorystore.Memory{}, invalidArgument("memory primary repository is not configured")
	}
	if s.repositories == nil {
		return memorystore.Memory{}, invalidArgument("memory repository provisioner is not configured")
	}
	if s.repositoryStore == nil {
		return memorystore.Memory{}, invalidArgument("resource repository store is not configured")
	}
	if s.repositoryForker == nil {
		return memorystore.Memory{}, invalidArgument("memory repository forker is not configured")
	}
	now := s.now()
	forkID := newMemoryID()
	name := strings.TrimSpace(input.Name)
	if name == "" {
		name = firstNonEmptyString(strings.TrimSpace(source.Name)+" fork", "Memory fork")
	}
	slug := memoryForkSlug(firstNonEmptyString(input.RepoNameOverride, name, source.Slug, source.Name), forkID)
	if slug == "" {
		return memorystore.Memory{}, invalidArgument("memory slug is required")
	}
	storage, err := reposservice.ResolveRepositoryStorage(workspace, subject, input.StorageScope)
	if err != nil {
		return memorystore.Memory{}, err
	}
	repoName := memoryRepositoryName(slug)
	repoFullName := reposservice.RepositoryFullName(storage.OwnerLogin, repoName)
	m := memorystore.Memory{
		ID:                forkID,
		Slug:              slug,
		Name:              name,
		Description:       firstNonEmptyString(strings.TrimSpace(input.Description), source.Description),
		StorageScope:      storage.Scope,
		StorageOwnerLogin: storage.OwnerLogin,
		IsActive:          true,
		CreatedBy:         subject,
		UpdatedBy:         subject,
		CreatedAt:         now,
		UpdatedAt:         now,
	}
	if err := reposservice.EnsureRepositoryFullNameAvailable(ctx, s.repositoryStore, repoFullName, reposstore.ResourceTypeMemory, m.ID, reposstore.RepoRolePrimary); err != nil {
		return memorystore.Memory{}, err
	}
	suggestedRepoName, err := reposservice.FirstAvailableRepositoryNameStem(ctx, s.repositoryStore, storage.OwnerLogin, slug, nil, ".mem")
	if err != nil {
		return memorystore.Memory{}, err
	}
	repoResult, err := s.repositories.EnsureRepository(ctx, memoryRepositoryEnsureRequest(workspace, m.ID, storage.OwnerLogin, repoName, name, reposservice.RepositoryNameFieldName, suggestedRepoName, firstNonEmptyString(sourceRepos.Primary.DefaultRef, "main"), input.Actor))
	if err != nil {
		return memorystore.Memory{}, fmt.Errorf("ensure forked memory repository: %w", err)
	}
	sourceRepo := reposservice.IdentityFromStore(*sourceRepos.Primary)
	if err := s.repositoryForker.ForkMemoryRepository(ctx, MemoryRepositoryForkRequest{
		Source:  sourceRepo,
		Target:  repoResult,
		Message: "Fork memory " + source.Name,
	}); err != nil {
		failureRepos := []reposstore.ResourceRepository{reposservice.ResourceRepositoryFailureRecord(workspace, reposstore.ResourceTypeMemory, m.ID, reposstore.RepoRolePrimary, storage, repoResult, subject, now)}
		_ = reposservice.RecordResourceRepositoryLifecycleFailure(ctx, s.repositoryStore, failureRepos, reposstore.ResourceRepositoryLifecycleOperationCreateBind, subject, now, err)
		s.recordMemoryRepoLifecycleFailedActivity(ctx, failureRepos, subject, now, err)
		return memorystore.Memory{}, fmt.Errorf("fork memory repository: %w", err)
	}

	created, err := s.persistOwnedMemory(ctx, workspace, subject, m, repoResult, storage, firstNonEmptyString(repoResult.DefaultBranch, sourceRepos.Primary.DefaultRef, "main"), now, memoryActivityForked, map[string]any{"name": m.Name, "slug": m.Slug, "sourceMemoryId": source.ID, "sourceMemoryName": source.Name, "sourceName": source.Name})
	if err != nil {
		failureRepos := []reposstore.ResourceRepository{reposservice.ResourceRepositoryFailureRecord(workspace, reposstore.ResourceTypeMemory, m.ID, reposstore.RepoRolePrimary, storage, repoResult, subject, now)}
		_ = reposservice.RecordResourceRepositoryLifecycleFailure(ctx, s.repositoryStore, failureRepos, reposstore.ResourceRepositoryLifecycleOperationCreateBind, subject, now, err)
		s.recordMemoryRepoLifecycleFailedActivity(ctx, failureRepos, subject, now, err)
		return memorystore.Memory{}, err
	}
	created.Role = "owner"
	created.CanManage = true
	return created, nil
}

func (s *MemoryService) GetMemory(ctx context.Context, workspaceID, subject, id string) (memorystore.Memory, error) {
	if s.store == nil {
		return memorystore.Memory{}, invalidArgument("memory service is not configured")
	}
	if err := validateSubject(subject); err != nil {
		return memorystore.Memory{}, err
	}
	m, err := s.store.GetMemory(ctx, strings.TrimSpace(workspaceID), strings.TrimSpace(id))
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return memorystore.Memory{}, notFound("memory not found")
		}
		return memorystore.Memory{}, err
	}
	role := s.memoryRole(ctx, workspaceID, m, subject)
	if strings.TrimSpace(role) == "" {
		return memorystore.Memory{}, forbidden("memory is not accessible")
	}
	m.Role = role
	m.CanManage = roleCanManage(role)
	if err := s.store.DecorateMemoryPin(ctx, strings.TrimSpace(workspaceID), strings.TrimSpace(subject), &m); err != nil {
		return memorystore.Memory{}, err
	}
	return m, nil
}

func (s *MemoryService) GetMemoryRepositories(ctx context.Context, workspaceID, subject, id string) (MemoryRepositories, error) {
	m, err := s.GetMemory(ctx, workspaceID, subject, id)
	if err != nil {
		return MemoryRepositories{}, err
	}
	if s.repositoryStore == nil {
		return MemoryRepositories{}, invalidArgument("resource repository store is not configured")
	}
	repos, err := s.repositoryStore.ListForResource(ctx, strings.TrimSpace(workspaceID), reposstore.ResourceTypeMemory, m.ID)
	if err != nil {
		return MemoryRepositories{}, err
	}
	result := MemoryRepositories{}
	for i := range repos {
		switch strings.TrimSpace(repos[i].RepoRole) {
		case reposstore.RepoRolePrimary:
			result.Primary = &repos[i]
		}
	}
	return result, nil
}

func (s *MemoryService) ListMemories(ctx context.Context, workspaceID, subject, view, query string, page, perPage int) (MemoryCollectionPage, error) {
	if s.store == nil {
		return MemoryCollectionPage{}, invalidArgument("memory service is not configured")
	}
	if err := validateSubject(subject); err != nil {
		return MemoryCollectionPage{}, err
	}
	visible, err := s.store.ListVisibleMemories(ctx, workspaceID, subject, true)
	if err != nil {
		return MemoryCollectionPage{}, err
	}
	activity.RefreshVisibleResourceActivity(ctx, s.activityProjector, s.repositoryStore, workspaceID, reposstore.ResourceTypeMemory, memoryActivityVisibleAssets(visible), s.activityRefreshAfter, nil)
	result, err := s.store.ListMemoryCollection(ctx, memorystore.MemoryCollectionOptions{
		WorkspaceID: strings.TrimSpace(workspaceID),
		PrincipalID: strings.TrimSpace(subject),
		View:        strings.TrimSpace(view),
		Query:       strings.TrimSpace(query),
		Page:        page,
		PerPage:     perPage,
	})
	if err != nil {
		return MemoryCollectionPage{}, err
	}
	for i := range result.Items {
		role := s.memoryRole(ctx, workspaceID, result.Items[i], subject)
		result.Items[i].Role = role
		result.Items[i].CanManage = roleCanManage(role)
	}
	return MemoryCollectionPage{Items: result.Items, Total: result.Total, Page: result.Page, PerPage: result.PerPage, HasMore: result.HasMore}, nil
}

func (s *MemoryService) CountWorkspaceMemories(ctx context.Context, workspaceID string, subject string) (int64, error) {
	if s.store == nil {
		return 0, invalidArgument("memory service is not configured")
	}
	owner := strings.TrimSpace(subject)
	if owner == "" {
		return 0, invalidArgument("subject is required")
	}
	return s.store.CountWorkspaceMemories(ctx, strings.TrimSpace(workspaceID), owner)
}

func (s *MemoryService) UpdateMemory(ctx context.Context, workspaceID, subject, id string, input MemoryUpdateMutation) (memorystore.Memory, error) {
	if s.store == nil {
		return memorystore.Memory{}, invalidArgument("memory service is not configured")
	}
	if err := validateSubject(subject); err != nil {
		return memorystore.Memory{}, err
	}
	m, err := s.GetMemory(ctx, workspaceID, subject, id)
	if err != nil {
		return memorystore.Memory{}, err
	}
	if !s.canManageMemory(ctx, workspaceID, m, subject) {
		return memorystore.Memory{}, forbidden("memory requires manage access")
	}
	before := m
	now := s.now()
	if name := strings.TrimSpace(input.Name); name != "" {
		m.Name = name
	}
	if slug := strings.TrimSpace(input.Slug); slug != "" {
		m.Slug = memorySlugForName(slug)
	}
	if input.Description != "" {
		m.Description = strings.TrimSpace(input.Description)
	}
	if input.IsActive != nil {
		m.IsActive = *input.IsActive
	}
	m.UpdatedBy = strings.TrimSpace(subject)
	m.UpdatedAt = now
	repoFullName := s.memoryPrimaryRepoFullName(ctx, strings.TrimSpace(workspaceID), m.ID)
	var updated memorystore.Memory
	err = s.store.Transaction(ctx, func(tx *memorystore.Store) error {
		var err error
		updated, err = tx.UpdateMemory(ctx, m)
		if err != nil {
			return err
		}
		return writeMemoryActivity(ctx, s.memoryActivityStore(tx), workspaceID, updated, repoFullName, memoryActivitySettingsChanged, strings.TrimSpace(subject), "settings", updated.ID, memorySettingsDetails(before, updated), now)
	})
	if err != nil {
		return memorystore.Memory{}, err
	}
	return updated, nil
}

func (s *MemoryService) archiveMemory(ctx context.Context, workspaceID, subject, id string) error {
	if s.store == nil {
		return invalidArgument("memory service is not configured")
	}
	if s.repositoryStore == nil {
		return invalidArgument("resource repository store is not configured")
	}
	if err := validateSubject(subject); err != nil {
		return err
	}
	m, err := s.GetMemory(ctx, workspaceID, subject, id)
	if err != nil {
		return err
	}
	if !s.canManageMemory(ctx, workspaceID, m, subject) {
		return forbidden("memory requires manage access")
	}
	now := s.now()
	repos, err := s.repositoryStore.ListForResource(ctx, strings.TrimSpace(workspaceID), reposstore.ResourceTypeMemory, m.ID)
	if err != nil {
		return err
	}
	if err := reposservice.DeleteManagedResourceRepositories(ctx, s.repositoryStore, s.repositoryDeleter, repos, subject, now); err != nil {
		s.recordMemoryRepoLifecycleFailedActivity(ctx, repos, subject, now, err)
		return err
	}
	repoFullName := memoryPrimaryRepoFullNameFromRepos(repos)
	m.IsActive = false
	m.DeletedAt = &now
	m.UpdatedBy = strings.TrimSpace(subject)
	m.UpdatedAt = now
	err = s.store.Transaction(ctx, func(tx *memorystore.Store) error {
		if _, err := tx.UpdateMemory(ctx, m); err != nil {
			return err
		}
		deletedRepos, err := s.repositoryStore.WithDB(tx.DB()).DeleteForResource(ctx, strings.TrimSpace(workspaceID), reposstore.ResourceTypeMemory, m.ID)
		if err != nil {
			return err
		}
		for _, repo := range deletedRepos {
			if err := activity.WriteResourceRepositoryLifecycleActivity(ctx, s.memoryActivityStore(tx), repo, activity.ResourceRepositoryActivityDeleted, strings.TrimSpace(subject), now); err != nil {
				return err
			}
		}
		return writeMemoryActivity(ctx, s.memoryActivityStore(tx), workspaceID, m, repoFullName, memoryActivityArchived, strings.TrimSpace(subject), "resource", m.ID, map[string]any{"name": m.Name}, now)
	})
	if err != nil {
		_ = reposservice.RecordResourceRepositoryLifecycleFailure(ctx, s.repositoryStore, repos, reposstore.ResourceRepositoryLifecycleOperationDeleteCleanup, subject, now, err)
		s.recordMemoryRepoLifecycleFailedActivity(ctx, repos, subject, now, err)
	}
	return err
}

func (s *MemoryService) DeleteMemory(ctx context.Context, workspaceID, subject, id string) error {
	if s.store == nil {
		return invalidArgument("memory service is not configured")
	}
	if s.repositoryStore == nil {
		return invalidArgument("resource repository store is not configured")
	}
	if err := validateSubject(subject); err != nil {
		return err
	}
	m, err := s.GetMemory(ctx, workspaceID, subject, id)
	if err != nil {
		return err
	}
	if !s.canManageMemory(ctx, workspaceID, m, subject) {
		return forbidden("memory requires manage access")
	}
	repos, err := s.repositoryStore.ListForResource(ctx, strings.TrimSpace(workspaceID), reposstore.ResourceTypeMemory, m.ID)
	if err != nil {
		return err
	}
	now := s.now()
	if err := reposservice.DeleteManagedResourceRepositories(ctx, s.repositoryStore, s.repositoryDeleter, repos, subject, now); err != nil {
		s.recordMemoryRepoLifecycleFailedActivity(ctx, repos, subject, now, err)
		return err
	}
	repoFullName := memoryPrimaryRepoFullNameFromRepos(repos)
	err = s.store.Transaction(ctx, func(tx *memorystore.Store) error {
		if err := tx.DeleteMemory(ctx, strings.TrimSpace(workspaceID), m.ID); err != nil {
			return err
		}
		deletedRepos, err := s.repositoryStore.WithDB(tx.DB()).DeleteForResource(ctx, strings.TrimSpace(workspaceID), reposstore.ResourceTypeMemory, m.ID)
		if err != nil {
			return err
		}
		for _, repo := range deletedRepos {
			if err := activity.WriteResourceRepositoryLifecycleActivity(ctx, s.memoryActivityStore(tx), repo, activity.ResourceRepositoryActivityDeleted, strings.TrimSpace(subject), now); err != nil {
				return err
			}
		}
		return writeMemoryActivity(ctx, s.memoryActivityStore(tx), workspaceID, m, repoFullName, memoryActivityDeleted, strings.TrimSpace(subject), "resource", m.ID, map[string]any{"name": m.Name}, now)
	})
	if err != nil {
		_ = reposservice.RecordResourceRepositoryLifecycleFailure(ctx, s.repositoryStore, repos, reposstore.ResourceRepositoryLifecycleOperationDeleteCleanup, subject, now, err)
		s.recordMemoryRepoLifecycleFailedActivity(ctx, repos, subject, now, err)
	}
	return err
}

func (s *MemoryService) PinMemory(ctx context.Context, workspaceID, subject, id string) error {
	if s.store == nil {
		return invalidArgument("memory service is not configured")
	}
	if err := validateSubject(subject); err != nil {
		return err
	}
	_, err := s.GetMemory(ctx, workspaceID, subject, id)
	if err != nil {
		return err
	}
	_, err = s.store.UpsertMemoryPin(ctx, memorystore.MemoryPin{
		WorkspaceID: strings.TrimSpace(workspaceID),
		PrincipalID: strings.TrimSpace(subject),
		MemoryID:    strings.TrimSpace(id),
		CreatedAt:   s.now(),
		UpdatedAt:   s.now(),
	})
	return err
}

func (s *MemoryService) UnpinMemory(ctx context.Context, workspaceID, subject, id string) error {
	if s.store == nil {
		return invalidArgument("memory service is not configured")
	}
	if err := validateSubject(subject); err != nil {
		return err
	}
	if _, err := s.GetMemory(ctx, workspaceID, subject, id); err != nil {
		return err
	}
	return s.store.DeleteMemoryPin(ctx, strings.TrimSpace(workspaceID), strings.TrimSpace(subject), strings.TrimSpace(id))
}

var memorySlugCleanupPattern = regexp.MustCompile(`[^a-z0-9]+`)

func memorySlugForName(name string) string {
	slug := strings.ToLower(strings.TrimSpace(name))
	slug = memorySlugCleanupPattern.ReplaceAllString(slug, "-")
	slug = strings.Trim(slug, "-")
	if slug == "" {
		return "memory"
	}
	return slug
}

func memoryForkSlug(base, memoryID string) string {
	slug := memorySlugForName(base)
	suffix := strings.TrimPrefix(strings.TrimSpace(memoryID), "mem_")
	if len(suffix) > 8 {
		suffix = suffix[:8]
	}
	if suffix == "" {
		return slug
	}
	return slug + "-" + suffix
}

func newMemoryID() string {
	id, err := identifiers.NewEntityID("mem")
	if err != nil {
		return fmt.Sprintf("mem_%d", time.Now().UnixNano())
	}
	return id
}
