package authorization

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/authelia/authelia/v4/internal/utils"
)

// NewAccessControlDomain creates a new SubjectObjectMatcher that matches the domain as a basic string.
func NewAccessControlDomain(domain string) (subjcets bool, rule AccessControlDomain) {
	m := &AccessControlDomainMatcher{}
	domain = strings.ToLower(domain)

	switch {
	case strings.HasPrefix(domain, "*."):
		m.Wildcard = true
		m.Name = domain[1:]
	case strings.HasPrefix(domain, "{user}"):
		m.UserWildcard = true
		m.Name = domain[6:]
	case strings.HasPrefix(domain, "{group}"):
		m.GroupWildcard = true
		m.Name = domain[7:]
	default:
		m.Name = domain
	}

	return m.UserWildcard || m.GroupWildcard, AccessControlDomain{m}
}

// NewAccessControlDomainRegex creates a new SubjectObjectMatcher that matches the domain either in a basic way or
// dynamic User/Group subexpression group way.
func NewAccessControlDomainRegex(pattern regexp.Regexp) (subjects bool, rule AccessControlDomain) {
	var iuser, igroup = -1, -1

	for i, group := range pattern.SubexpNames() {
		switch group {
		case subexpNameUser:
			iuser = i
		case subexpNameGroup:
			igroup = i
		}
	}

	if iuser != -1 || igroup != -1 {
		return true, AccessControlDomain{RegexpGroupStringSubjectMatcher{pattern, iuser, igroup}}
	}

	return false, AccessControlDomain{RegexpStringSubjectMatcher{pattern}}
}

// AccessControlDomainMatcher is the basic domain matcher.
type AccessControlDomainMatcher struct {
	Name          string
	Wildcard      bool
	UserWildcard  bool
	GroupWildcard bool
}

// IsMatch returns true if this rule matches.
func (m AccessControlDomainMatcher) IsMatch(domain string, subject Subject) (match bool) {
	switch {
	case m.Wildcard:
		return strings.HasSuffix(domain, m.Name)
	case m.UserWildcard:
		if subject.IsAnonymous() && strings.HasSuffix(domain, m.Name) {
			return len(domain) > len(m.Name)
		}

		return domain == fmt.Sprintf("%s%s", subject.Username, m.Name)
	case m.GroupWildcard:
		if subject.IsAnonymous() && strings.HasSuffix(domain, m.Name) {
			return len(domain) > len(m.Name)
		}

		i := strings.Index(domain, ".")

		return domain[i:] == m.Name && utils.IsStringInSliceFold(domain[:i], subject.Groups)
	default:
		return strings.EqualFold(domain, m.Name)
	}
}

// AccessControlDomain represents an ACL domain.
type AccessControlDomain struct {
	Matcher StringSubjectMatcher
}

// IsMatch returns true if the ACL domain matches the object domain.
func (acl AccessControlDomain) IsMatch(subject Subject, object Object) (match bool) {
	return acl.Matcher.IsMatch(object.Domain, subject)
}
