package memory

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/service/content"
	memorystore "github.com/tidbcloud/AgentTeam/internal/store/resource/memory"
	"gorm.io/gorm"
)

const memoryContentDomain = "memory"

// MemoryContentAccessPolicy rechecks live workspace membership and the current
// managed-Memory grant at the Content Store boundary. Request-local scope is
// necessary but never sufficient because membership and grants can be revoked
// after a client session was issued.
type MemoryContentAccessPolicy struct {
	workspace content.WorkspaceAccessVerifier
	memories  memoryContentAccessStore
}

type memoryContentAccessStore interface {
	GetMemory(context.Context, string, string) (memorystore.Memory, error)
	GetMemoryGrant(context.Context, string, string, string, string) (memorystore.MemoryGrant, error)
}

func NewMemoryContentAccessPolicy(workspace content.WorkspaceAccessVerifier, memories memoryContentAccessStore) (*MemoryContentAccessPolicy, error) {
	if workspace == nil || memories == nil {
		return nil, fmt.Errorf("memory content workspace verifier and memory store are required")
	}
	return &MemoryContentAccessPolicy{workspace: workspace, memories: memories}, nil
}

func (policy *MemoryContentAccessPolicy) AuthorizeContentAccess(ctx context.Context, request content.AccessRequest) error {
	if policy == nil || policy.workspace == nil || policy.memories == nil || strings.TrimSpace(request.Domain) != memoryContentDomain {
		return content.ErrAccessDenied
	}
	if err := (content.ScopedAccessPolicy{}).AuthorizeContentAccess(ctx, request); err != nil {
		return err
	}
	workspaceID := strings.TrimSpace(request.WorkspaceID)
	memoryID := strings.TrimSpace(request.BindingKey)
	subject := strings.TrimSpace(request.Principal.Subject)
	allowed, err := policy.workspace.HasWorkspaceAccess(ctx, workspaceID, subject)
	if err != nil {
		return fmt.Errorf("verify memory content workspace access: %w", err)
	}
	if !allowed {
		return content.ErrAccessDenied
	}
	memory, err := policy.memories.GetMemory(ctx, workspaceID, memoryID)
	if errors.Is(err, gorm.ErrRecordNotFound) || (err == nil && (!memory.IsActive || memory.DeletedAt != nil)) {
		return content.ErrAccessDenied
	}
	if err != nil {
		return fmt.Errorf("load memory content owner: %w", err)
	}
	// Memory rows are globally identified and do not carry workspace_id. The
	// workspace-scoped grant is therefore also the binding-to-workspace proof;
	// CreatedBy alone must never authorize a same-subject request in another
	// workspace.
	role := ""
	if grant, grantErr := policy.memories.GetMemoryGrant(ctx, workspaceID, memoryID, "user", subject); grantErr == nil {
		role = normalizeMemoryGrantRole(grant.Role, grant.SubjectType)
	} else if !errors.Is(grantErr, gorm.ErrRecordNotFound) {
		return fmt.Errorf("load memory content grant: %w", grantErr)
	}
	if !memoryContentRoleAllows(role, request.Action) {
		return content.ErrAccessDenied
	}
	return nil
}

func memoryContentRoleAllows(role string, action content.AccessAction) bool {
	role = strings.ToLower(strings.TrimSpace(role))
	if role == "edit" {
		role = "view"
	}
	read := role == "owner" || role == "manage" || role == "view"
	manage := role == "owner" || role == "manage"
	switch action {
	case content.AccessReadMetadata, content.AccessReadPayload,
		content.AccessReadHistory, content.AccessCompare,
		content.AccessReadArchive:
		return read
	case content.AccessCreateLineage, content.AccessPublish,
		content.AccessPublishDetachedMigration, content.AccessManagePin:
		return manage
	default:
		return false
	}
}

var _ content.ContentAccessPolicy = (*MemoryContentAccessPolicy)(nil)
