package content

import (
	"context"
	"errors"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/identity/trusted"
)

// AccessAction is the closed set of operations enforced at the Content Store
// service boundary. Adding an action requires an explicit policy decision.
type AccessAction string

const (
	AccessCreateLineage            AccessAction = "create_lineage"
	AccessReadMetadata             AccessAction = "read_metadata"
	AccessReadPayload              AccessAction = "read_payload"
	AccessPublish                  AccessAction = "publish_version"
	AccessPublishDetachedMigration AccessAction = "publish_detached_migration_version"
	AccessReadHistory              AccessAction = "read_history"
	AccessCompare                  AccessAction = "compare_versions"
	AccessReadArchive              AccessAction = "read_archive"
	AccessManagePin                AccessAction = "manage_pin"
)

var (
	ErrAccessDenied      = errors.New("content access denied")
	ErrPolicyUnavailable = errors.New("content access policy unavailable")
)

// AccessPrincipal is a trusted, request-local identity scope. Domain services
// may narrow a human or service identity to one exact binding/content and
// action set; it is never persisted or returned as a reusable credential.
type AccessPrincipal struct {
	Subject string
	// SubjectType is the owning domain's explicit principal kind. Domains
	// whose ACL distinguishes users from agents must set and validate it;
	// leaving it empty preserves the existing human-only domain contracts.
	SubjectType string
	WorkspaceID string
	Domain      string
	BindingKey  string
	ContentID   string
	Generation  uint64
	Actions     []AccessAction
}

// AccessRequest contains only server-resolved authorization facts.
type AccessRequest struct {
	Principal   AccessPrincipal
	WorkspaceID string
	Domain      string
	BindingKey  string
	ContentID   string
	VersionIDs  []string
	Generation  uint64
	Action      AccessAction
}

// ContentAccessPolicy authorizes one exact request. Implementations must deny
// unknown domains/actions and unavailable dependencies.
type ContentAccessPolicy interface {
	AuthorizeContentAccess(context.Context, AccessRequest) error
}

type accessPrincipalKey struct{}

// WithAccessPrincipal attaches a narrowly scoped identity established by an
// owning domain service or worker. Empty and internally inconsistent scopes
// are rejected later by the mandatory policy.
func WithAccessPrincipal(ctx context.Context, principal AccessPrincipal) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	return context.WithValue(ctx, accessPrincipalKey{}, principal)
}

func principalFromContext(ctx context.Context, workspaceID string) AccessPrincipal {
	if principal, ok := ctx.Value(accessPrincipalKey{}).(AccessPrincipal); ok {
		return principal
	}
	identity, _ := trusted.IdentityFromContext(ctx)
	return AccessPrincipal{Subject: strings.TrimSpace(identity.Subject), WorkspaceID: strings.TrimSpace(workspaceID)}
}

// ScopedAccessPolicy is the production fail-closed policy for domain calls
// that have already resolved and narrowed their business authorization. It
// intentionally denies generic human access that carries only a subject and
// workspace; generic Content Store routes are therefore not a second public
// content API.
type ScopedAccessPolicy struct{}

func (ScopedAccessPolicy) AuthorizeContentAccess(_ context.Context, request AccessRequest) error {
	principal := request.Principal
	if strings.TrimSpace(principal.Subject) == "" ||
		strings.TrimSpace(principal.WorkspaceID) == "" ||
		strings.TrimSpace(principal.WorkspaceID) != strings.TrimSpace(request.WorkspaceID) ||
		strings.TrimSpace(principal.Domain) == "" ||
		strings.TrimSpace(principal.Domain) != strings.TrimSpace(request.Domain) {
		return ErrAccessDenied
	}
	if binding := strings.TrimSpace(principal.BindingKey); binding != "" && binding != strings.TrimSpace(request.BindingKey) {
		return ErrAccessDenied
	}
	if contentID := strings.TrimSpace(principal.ContentID); contentID != "" && contentID != strings.TrimSpace(request.ContentID) {
		return ErrAccessDenied
	}
	if strings.TrimSpace(principal.BindingKey) == "" && strings.TrimSpace(principal.ContentID) == "" {
		return ErrAccessDenied
	}
	if principal.Generation != 0 && principal.Generation != request.Generation {
		return ErrAccessDenied
	}
	for _, action := range principal.Actions {
		if action == request.Action && knownAccessAction(action) {
			return nil
		}
	}
	return ErrAccessDenied
}

func knownAccessAction(action AccessAction) bool {
	switch action {
	case AccessCreateLineage, AccessReadMetadata, AccessReadPayload, AccessPublish, AccessPublishDetachedMigration,
		AccessReadHistory, AccessCompare, AccessReadArchive, AccessManagePin:
		return true
	default:
		return false
	}
}
