package access

import (
	"errors"
	"fmt"
	"strings"
)

// ErrInvalidScopeTarget marks a malformed resolved scope target/access contract.
var ErrInvalidScopeTarget = errors.New("invalid scope target")

// ScopeTargetKind is the product-facing kind of a target that resolves to one
// or more scope roles. It intentionally uses scope terminology even when a
// compatibility API still exposes older channel/thread/task target names.
type ScopeTargetKind string

const (
	ScopeTargetKindChannel ScopeTargetKind = "channel"
	ScopeTargetKindDM      ScopeTargetKind = "dm"
	ScopeTargetKindThread  ScopeTargetKind = "thread"
	ScopeTargetKindTask    ScopeTargetKind = "task"
	// ScopeTargetKindServiceAccount is the dedicated message scope owned by a
	// workspace-local service-account instance. It intentionally remains a
	// normal scope target so history, reactions, pins, and read state share the
	// canonical message authorization contract.
	ScopeTargetKindServiceAccount ScopeTargetKind = "service_account"
)

// ScopeTargetOperation names the operation whose resolver produced the target.
// Callers should prefer a stable operation name over domain-local booleans so
// reviews can tell whether storage, delivery, read, and permission roles were
// resolved for the same user intent.
type ScopeTargetOperation string

const (
	ScopeTargetOperationMessageSend          ScopeTargetOperation = "message_send"
	ScopeTargetOperationMessageRead          ScopeTargetOperation = "message_read"
	ScopeTargetOperationAttachmentUpload     ScopeTargetOperation = "attachment_upload"
	ScopeTargetOperationAttachmentRead       ScopeTargetOperation = "attachment_read"
	ScopeTargetOperationReactionWrite        ScopeTargetOperation = "reaction_write"
	ScopeTargetOperationMessagePinWrite      ScopeTargetOperation = "message_pin_write"
	ScopeTargetOperationServiceEventDelivery ScopeTargetOperation = "service_event_delivery"
	ScopeTargetOperationTaskDiscussion       ScopeTargetOperation = "task_discussion"
)

// ScopeAuthoritySubjectKind identifies why the subject participates in the
// resolver. Durable author, routing principal, notification subject, and
// service/execution authority are separate roles and must not be collapsed into
// whichever member ID happens to be convenient for a caller.
type ScopeAuthoritySubjectKind string

const (
	ScopeAuthoritySubjectDurableAuthor    ScopeAuthoritySubjectKind = "durable_author"
	ScopeAuthoritySubjectRoutingPrincipal ScopeAuthoritySubjectKind = "routing_principal"
	ScopeAuthoritySubjectNotifyPrincipal  ScopeAuthoritySubjectKind = "notify_principal"
	ScopeAuthoritySubjectServicePrincipal ScopeAuthoritySubjectKind = "service_principal"
	ScopeAuthoritySubjectExecutionOwner   ScopeAuthoritySubjectKind = "execution_owner"
)

// ScopePermissionBasisKind describes the fact that authorized an operation.
// The basis may differ from the storage or delivery scope. For example, a reply
// thread upload stores in the thread scope while a parent channel membership can
// be the permission basis.
type ScopePermissionBasisKind string

const (
	ScopePermissionBasisDirectMembership       ScopePermissionBasisKind = "direct_membership"
	ScopePermissionBasisParentMembership       ScopePermissionBasisKind = "parent_membership"
	ScopePermissionBasisParentDMParticipant    ScopePermissionBasisKind = "parent_dm_participant"
	ScopePermissionBasisOwnerPrivateMembership ScopePermissionBasisKind = "owner_private_membership"
	ScopePermissionBasisTaskBoardChannel       ScopePermissionBasisKind = "task_board_channel"
	ScopePermissionBasisTaskCollaboratorGrant  ScopePermissionBasisKind = "task_collaborator_grant"
	ScopePermissionBasisRootMessageVisibility  ScopePermissionBasisKind = "root_message_visibility"
	ScopePermissionBasisServiceGrant           ScopePermissionBasisKind = "service_grant"
	ScopePermissionBasisServiceAccountManager  ScopePermissionBasisKind = "service_account_manager"
	ScopePermissionBasisArchiveRecovery        ScopePermissionBasisKind = "archive_recovery"
	ScopePermissionBasisSystem                 ScopePermissionBasisKind = "system"
)

// ScopeTargetRef is the user- or API-facing target before role resolution.
type ScopeTargetRef struct {
	Kind ScopeTargetKind
	Ref  string
}

// ScopeRef is a concrete scope role in a resolved target.
type ScopeRef struct {
	ScopeID   string
	ScopeType ScopeTargetKind
}

// Empty reports whether the role is absent.
func (r ScopeRef) Empty() bool {
	return strings.TrimSpace(r.ScopeID) == "" && strings.TrimSpace(string(r.ScopeType)) == ""
}

// ScopeAuthoritySubject identifies the actor/principal/member role considered
// by a resolver. A resolver may emit multiple subjects when durable author,
// routing principal, and notification subject differ.
type ScopeAuthoritySubject struct {
	Kind           ScopeAuthoritySubjectKind
	PrincipalType  PrincipalType
	PrincipalID    string
	MemberID       string
	InstallationID string
}

// ScopePermissionBasis records the explicit authorization basis used to allow a
// resolved scope operation.
type ScopePermissionBasis struct {
	Kind           ScopePermissionBasisKind
	Scope          ScopeRef
	DecisionSource DecisionSource
	MemberID       string
	OwnerMemberID  string
	GrantID        string
	Reason         string
}

// ResolvedScopeTarget is the shared target/access contract for message-domain
// resolvers. It separates where a fact is stored, where delivery/read semantics
// apply, and which scope or grant authorized the operation. Callers must not
// infer one role from another unless the resolver populated it explicitly.
type ResolvedScopeTarget struct {
	WorkspaceID string
	Operation   ScopeTargetOperation
	Requested   ScopeTargetRef

	Subjects []ScopeAuthoritySubject

	StorageScope  ScopeRef
	DeliveryScope ScopeRef
	ReadScope     ScopeRef
	ParentScope   ScopeRef

	RootMessageID    string
	RootMessageShort string

	PermissionBasis []ScopePermissionBasis
}

// Normalized returns a copy with surrounding whitespace removed from free-form
// identifiers. It does not invent missing roles.
func (target ResolvedScopeTarget) Normalized() ResolvedScopeTarget {
	target.WorkspaceID = strings.TrimSpace(target.WorkspaceID)
	target.Operation = ScopeTargetOperation(strings.TrimSpace(string(target.Operation)))
	target.Requested.Kind = ScopeTargetKind(strings.TrimSpace(string(target.Requested.Kind)))
	target.Requested.Ref = strings.TrimSpace(target.Requested.Ref)
	target.StorageScope = normalizeScopeRef(target.StorageScope)
	target.DeliveryScope = normalizeScopeRef(target.DeliveryScope)
	target.ReadScope = normalizeScopeRef(target.ReadScope)
	target.ParentScope = normalizeScopeRef(target.ParentScope)
	target.RootMessageID = strings.TrimSpace(target.RootMessageID)
	target.RootMessageShort = strings.TrimSpace(target.RootMessageShort)
	for i := range target.Subjects {
		target.Subjects[i] = normalizeScopeAuthoritySubject(target.Subjects[i])
	}
	for i := range target.PermissionBasis {
		target.PermissionBasis[i] = normalizeScopePermissionBasis(target.PermissionBasis[i])
	}
	return target
}

// Validate checks the durable contract shape. It intentionally validates roles,
// not product-specific authorization rules; domain resolvers still own their
// actual membership/grant lookups.
func (target ResolvedScopeTarget) Validate() error {
	normalized := target.Normalized()
	if normalized.WorkspaceID == "" {
		return invalidScopeTarget("workspace ID is required")
	}
	if normalized.Operation == "" {
		return invalidScopeTarget("operation is required")
	}
	if !validScopeTargetOperation(normalized.Operation) {
		return invalidScopeTarget("operation %q is invalid", normalized.Operation)
	}
	if err := validateScopeTargetRef(normalized.Requested, "requested target"); err != nil {
		return err
	}
	if normalized.StorageScope.Empty() && normalized.DeliveryScope.Empty() && normalized.ReadScope.Empty() {
		return invalidScopeTarget("at least one storage, delivery, or read scope role is required")
	}
	if err := validateOptionalScopeRef(normalized.StorageScope, "storage scope"); err != nil {
		return err
	}
	if err := validateOptionalScopeRef(normalized.DeliveryScope, "delivery scope"); err != nil {
		return err
	}
	if err := validateOptionalScopeRef(normalized.ReadScope, "read scope"); err != nil {
		return err
	}
	if err := validateOptionalScopeRef(normalized.ParentScope, "parent scope"); err != nil {
		return err
	}
	if (normalized.RootMessageID != "" || normalized.RootMessageShort != "") && normalized.ParentScope.Empty() {
		return invalidScopeTarget("root message roles require a parent scope")
	}
	if len(normalized.Subjects) == 0 {
		return invalidScopeTarget("at least one authority subject is required")
	}
	for i, subject := range normalized.Subjects {
		if err := validateScopeAuthoritySubject(subject, i); err != nil {
			return err
		}
	}
	if len(normalized.PermissionBasis) == 0 {
		return invalidScopeTarget("permission basis is required")
	}
	for i, basis := range normalized.PermissionBasis {
		if err := validateScopePermissionBasis(basis, i); err != nil {
			return err
		}
	}
	return nil
}

func normalizeScopeRef(ref ScopeRef) ScopeRef {
	return ScopeRef{
		ScopeID:   strings.TrimSpace(ref.ScopeID),
		ScopeType: ScopeTargetKind(strings.TrimSpace(string(ref.ScopeType))),
	}
}

func normalizeScopeAuthoritySubject(subject ScopeAuthoritySubject) ScopeAuthoritySubject {
	return ScopeAuthoritySubject{
		Kind:           ScopeAuthoritySubjectKind(strings.TrimSpace(string(subject.Kind))),
		PrincipalType:  PrincipalType(strings.TrimSpace(string(subject.PrincipalType))),
		PrincipalID:    strings.TrimSpace(subject.PrincipalID),
		MemberID:       strings.TrimSpace(subject.MemberID),
		InstallationID: strings.TrimSpace(subject.InstallationID),
	}
}

func normalizeScopePermissionBasis(basis ScopePermissionBasis) ScopePermissionBasis {
	return ScopePermissionBasis{
		Kind:           ScopePermissionBasisKind(strings.TrimSpace(string(basis.Kind))),
		Scope:          normalizeScopeRef(basis.Scope),
		DecisionSource: DecisionSource(strings.TrimSpace(string(basis.DecisionSource))),
		MemberID:       strings.TrimSpace(basis.MemberID),
		OwnerMemberID:  strings.TrimSpace(basis.OwnerMemberID),
		GrantID:        strings.TrimSpace(basis.GrantID),
		Reason:         strings.TrimSpace(basis.Reason),
	}
}

func validateScopeTargetRef(ref ScopeTargetRef, label string) error {
	if ref.Ref == "" {
		return invalidScopeTarget("%s ref is required", label)
	}
	if !validScopeTargetKind(ref.Kind) {
		return invalidScopeTarget("%s kind %q is invalid", label, ref.Kind)
	}
	return nil
}

func validateScopeAuthoritySubject(subject ScopeAuthoritySubject, index int) error {
	if strings.TrimSpace(string(subject.Kind)) == "" {
		return invalidScopeTarget("authority subject %d kind is required", index)
	}
	if !validScopeAuthoritySubjectKind(subject.Kind) {
		return invalidScopeTarget("authority subject %d kind %q is invalid", index, subject.Kind)
	}
	if subject.PrincipalID == "" && subject.MemberID == "" && subject.InstallationID == "" {
		return invalidScopeTarget("authority subject %d must name a principal, member, or installation", index)
	}
	return nil
}

func validateOptionalScopeRef(ref ScopeRef, label string) error {
	if ref.Empty() {
		return nil
	}
	if ref.ScopeID == "" {
		return invalidScopeTarget("%s ID is required when scope type is set", label)
	}
	if !validScopeTargetKind(ref.ScopeType) {
		return invalidScopeTarget("%s type %q is invalid", label, ref.ScopeType)
	}
	return nil
}

func validateScopePermissionBasis(basis ScopePermissionBasis, index int) error {
	if strings.TrimSpace(string(basis.Kind)) == "" {
		return invalidScopeTarget("permission basis %d kind is required", index)
	}
	if !validScopePermissionBasisKind(basis.Kind) {
		return invalidScopeTarget("permission basis %d kind %q is invalid", index, basis.Kind)
	}
	if err := validateOptionalScopeRef(basis.Scope, fmt.Sprintf("permission basis %d scope", index)); err != nil {
		return err
	}
	if basis.Scope.Empty() && strings.TrimSpace(string(basis.DecisionSource)) == "" && basis.GrantID == "" && basis.Reason == "" {
		return invalidScopeTarget("permission basis %d must name a scope, decision source, grant, or reason", index)
	}
	return nil
}

func validScopeTargetOperation(operation ScopeTargetOperation) bool {
	switch operation {
	case ScopeTargetOperationMessageSend,
		ScopeTargetOperationMessageRead,
		ScopeTargetOperationAttachmentUpload,
		ScopeTargetOperationAttachmentRead,
		ScopeTargetOperationReactionWrite,
		ScopeTargetOperationMessagePinWrite,
		ScopeTargetOperationServiceEventDelivery,
		ScopeTargetOperationTaskDiscussion:
		return true
	default:
		return false
	}
}

func validScopeTargetKind(kind ScopeTargetKind) bool {
	switch kind {
	case ScopeTargetKindChannel, ScopeTargetKindDM, ScopeTargetKindThread, ScopeTargetKindTask, ScopeTargetKindServiceAccount:
		return true
	default:
		return false
	}
}

func validScopeAuthoritySubjectKind(kind ScopeAuthoritySubjectKind) bool {
	switch kind {
	case ScopeAuthoritySubjectDurableAuthor,
		ScopeAuthoritySubjectRoutingPrincipal,
		ScopeAuthoritySubjectNotifyPrincipal,
		ScopeAuthoritySubjectServicePrincipal,
		ScopeAuthoritySubjectExecutionOwner:
		return true
	default:
		return false
	}
}

func validScopePermissionBasisKind(kind ScopePermissionBasisKind) bool {
	switch kind {
	case ScopePermissionBasisDirectMembership,
		ScopePermissionBasisParentMembership,
		ScopePermissionBasisParentDMParticipant,
		ScopePermissionBasisOwnerPrivateMembership,
		ScopePermissionBasisTaskBoardChannel,
		ScopePermissionBasisTaskCollaboratorGrant,
		ScopePermissionBasisRootMessageVisibility,
		ScopePermissionBasisServiceGrant,
		ScopePermissionBasisServiceAccountManager,
		ScopePermissionBasisArchiveRecovery,
		ScopePermissionBasisSystem:
		return true
	default:
		return false
	}
}

func invalidScopeTarget(format string, args ...any) error {
	return fmt.Errorf("%w: "+format, append([]any{ErrInvalidScopeTarget}, args...)...)
}
