package developmentmembership

import (
	"sort"
	"strings"

	core "github.com/authzed/spicedb/pkg/proto/core/v1"
	"github.com/authzed/spicedb/pkg/tuple"
)

// NewFoundSubject creates a new FoundSubject for a subject and a set of its resources.
func NewFoundSubject(subject *core.DirectSubject, resources ...tuple.ObjectAndRelation) FoundSubject {
	return FoundSubject{tuple.FromCoreObjectAndRelation(subject.Subject), nil, subject.CaveatExpression, NewONRSet(resources...)}
}

// FoundSubject contains a single found subject and all the relationships in which that subject
// is a member which were found via the ONRs expansion.
type FoundSubject struct {
	// subject is the subject found.
	subject tuple.ObjectAndRelation

	// excludedSubjects are any subjects excluded. Only should be set if subject is a wildcard.
	excludedSubjects []FoundSubject

	// caveatExpression is the conditional expression on the found subject.
	caveatExpression *core.CaveatExpression

	// resources are the resources under which the subject lives that informed the locating
	// of this subject for the root ONR.
	resources ONRSet
}

// GetSubjectId is named to match the Subject interface for the BaseSubjectSet.
//
//nolint:all
func (fs FoundSubject) GetSubjectId() string {
	return fs.subject.ObjectID
}

func (fs FoundSubject) GetCaveatExpression() *core.CaveatExpression {
	return fs.caveatExpression
}

func (fs FoundSubject) GetExcludedSubjects() []FoundSubject {
	return fs.excludedSubjects
}

// Subject returns the Subject of the FoundSubject.
func (fs FoundSubject) Subject() tuple.ObjectAndRelation {
	return fs.subject
}

// WildcardType returns the object type for the wildcard subject, if this is a wildcard subject.
func (fs FoundSubject) WildcardType() (string, bool) {
	if fs.subject.ObjectID == tuple.PublicWildcard {
		return fs.subject.ObjectType, true
	}

	return "", false
}

// ExcludedSubjectsFromWildcard returns those subjects excluded from the wildcard subject.
// If not a wildcard subject, returns false.
func (fs FoundSubject) ExcludedSubjectsFromWildcard() ([]FoundSubject, bool) {
	if fs.subject.ObjectID == tuple.PublicWildcard {
		return fs.excludedSubjects, true
	}

	return nil, false
}

func (fs FoundSubject) excludedSubjectStrings() []string {
	excludedStrings := make([]string, 0, len(fs.excludedSubjects))
	for _, excludedSubject := range fs.excludedSubjects {
		excludedSubjectString := tuple.StringONR(excludedSubject.subject)
		if excludedSubject.GetCaveatExpression() != nil {
			excludedSubjectString += "[...]"
		}
		excludedStrings = append(excludedStrings, excludedSubjectString)
	}

	sort.Strings(excludedStrings)
	return excludedStrings
}

// ToValidationString returns the FoundSubject in a format that is consumable by the validationfile
// package.
func (fs FoundSubject) ToValidationString() string {
	onrString := tuple.StringONR(fs.Subject())
	validationString := onrString
	if fs.caveatExpression != nil {
		validationString = validationString + "[...]"
	}

	excluded, isWildcard := fs.ExcludedSubjectsFromWildcard()
	if isWildcard && len(excluded) > 0 {
		validationString = validationString + " - {" + strings.Join(fs.excludedSubjectStrings(), ", ") + "}"
	}

	return validationString
}

func (fs FoundSubject) String() string {
	return fs.ToValidationString()
}

// ParentResources returns all the resources in which the subject was found as per the expand.
func (fs FoundSubject) ParentResources() []tuple.ObjectAndRelation {
	return fs.resources.AsSlice()
}

// FoundSubjects contains the subjects found for a specific ONR.
type FoundSubjects struct {
	// subjects is a map from the Subject ONR (as a string) to the FoundSubject information.
	subjects *TrackingSubjectSet
}

// ListFound returns a slice of all the FoundSubject's.
func (fs FoundSubjects) ListFound() []FoundSubject {
	return fs.subjects.ToSlice()
}

// LookupSubject returns the FoundSubject for a matching subject, if any.
func (fs FoundSubjects) LookupSubject(subject tuple.ObjectAndRelation) (FoundSubject, bool) {
	return fs.subjects.Get(subject)
}
