package policy_engine

import (
	"encoding/json"
	"fmt"
	"regexp"
	"slices"
	"sort"
	"strings"

	"github.com/seaweedfs/seaweedfs/weed/glog"
	s3const "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
	"github.com/seaweedfs/seaweedfs/weed/util/wildcard"
)

// Policy Engine Types
//
// This package provides enhanced AWS S3-compatible policy types with improved type safety.
//
// MIGRATION COMPLETE:
// This is now the unified PolicyDocument type used throughout the SeaweedFS codebase.
// The previous duplicate PolicyDocument types in iamapi and credential packages have
// been migrated to use these enhanced types, providing:
// - Principal specifications
// - Complex conditions (IP, time, string patterns, etc.)
// - Flexible string/array types with proper JSON marshaling
// - Policy compilation for performance
//
// All policy operations now use this single, consistent type definition.

// Constants for policy validation
const (
	// PolicyVersion2012_10_17 is the standard AWS policy version
	PolicyVersion2012_10_17 = "2012-10-17"
)

var (
	// PolicyVariableRegex detects AWS IAM policy variables like ${aws:username}
	PolicyVariableRegex = regexp.MustCompile(`\$\{([^}]+)\}`)

	// multipartActionSet contains all S3 multipart upload actions
	// These are treated as equivalent to s3:PutObject for authorization purposes
	multipartActionSet = map[string]bool{
		s3const.S3_ACTION_CREATE_MULTIPART:       true,
		s3const.S3_ACTION_UPLOAD_PART:            true,
		s3const.S3_ACTION_UPLOAD_PART_COPY:       true,
		s3const.S3_ACTION_COMPLETE_MULTIPART:     true,
		s3const.S3_ACTION_ABORT_MULTIPART:        true,
		s3const.S3_ACTION_LIST_PARTS:             true,
		s3const.S3_ACTION_LIST_MULTIPART_UPLOADS: true,
	}

	// lowerMultipartActionSet keys the same actions for case-insensitive lookup.
	lowerMultipartActionSet = func() map[string]bool {
		lowered := make(map[string]bool, len(multipartActionSet))
		for action := range multipartActionSet {
			lowered[strings.ToLower(action)] = true
		}
		return lowered
	}()
)

// StringOrStringSlice represents a value that can be either a string or []string
type StringOrStringSlice struct {
	values []string
}

// UnmarshalJSON implements json.Unmarshaler for StringOrStringSlice
func (s *StringOrStringSlice) UnmarshalJSON(data []byte) error {
	// Try unmarshaling as string first
	var str string
	if err := json.Unmarshal(data, &str); err == nil {
		s.values = []string{str}
		return nil
	}

	// Try unmarshaling as []string
	var strs []string
	if err := json.Unmarshal(data, &strs); err == nil {
		s.values = strs
		return nil
	}

	return fmt.Errorf("value must be string or []string")
}

// MarshalJSON implements json.Marshaler for StringOrStringSlice
func (s StringOrStringSlice) MarshalJSON() ([]byte, error) {
	if len(s.values) == 1 {
		return json.Marshal(s.values[0])
	}
	return json.Marshal(s.values)
}

// Strings returns the slice of strings. Nil-safe for pointer receivers.
func (s *StringOrStringSlice) Strings() []string {
	if s == nil {
		return nil
	}
	return s.values
}

// NewStringOrStringSlice creates a new StringOrStringSlice from strings
func NewStringOrStringSlice(values ...string) StringOrStringSlice {
	return StringOrStringSlice{values: values}
}

// NewStringOrStringSlicePtr creates a new *StringOrStringSlice from strings
func NewStringOrStringSlicePtr(values ...string) *StringOrStringSlice {
	return &StringOrStringSlice{values: values}
}

// CloneStringOrStringSlice returns a copy with its own backing slice.
func CloneStringOrStringSlice(value StringOrStringSlice) StringOrStringSlice {
	return StringOrStringSlice{values: append([]string(nil), value.values...)}
}

// allowedPrincipalKeys is the set of principal-type keys AWS allows inside the
// Principal/NotPrincipal object form.
var allowedPrincipalKeys = map[string]struct{}{
	"AWS":           {},
	"Service":       {},
	"Federated":     {},
	"CanonicalUser": {},
}

// PolicyPrincipal represents the Principal element of a policy statement.
//
// AWS accepts three shapes (see the IAM "Principal" element reference):
//
//	"Principal": "*"                                    // all principals (anonymous)
//	"Principal": { "AWS": "arn:aws:iam::123:user/x" }   // single, keyed by type
//	"Principal": { "AWS": ["arn:...", "999999999999"] } // array of values
//
// The object may key on "AWS", "Service", "Federated" or "CanonicalUser", and
// each value is itself a string or an array of strings. For backward
// compatibility SeaweedFS also accepts a bare string or array of strings.
//
// Authorization matches the request principal ARN against the flat set of
// principal values, so every keyed value is flattened into one list. The
// original JSON is preserved so GetBucketPolicy returns the exact shape the
// caller submitted; this keeps PutBucketPolicy/GetBucketPolicy idempotent for
// infrastructure-as-code tools (Terraform, Ansible) that diff the returned
// policy against the submitted one.
type PolicyPrincipal struct {
	values []string
	raw    json.RawMessage
}

// UnmarshalJSON implements json.Unmarshaler for PolicyPrincipal.
func (p *PolicyPrincipal) UnmarshalJSON(data []byte) error {
	// Preserve the original encoding for a faithful round-trip on marshal.
	p.raw = append(json.RawMessage(nil), data...)

	// Bare string, e.g. "*"
	var str string
	if err := json.Unmarshal(data, &str); err == nil {
		p.values = []string{str}
		return nil
	}

	// Bare array of strings (SeaweedFS extension; not standard AWS).
	var strs []string
	if err := json.Unmarshal(data, &strs); err == nil {
		p.values = strs
		return nil
	}

	// AWS object form: {"AWS": <string|[]string>, "Service": ..., "Federated": ...,
	// "CanonicalUser": ...}. Each value reuses StringOrStringSlice so it accepts a
	// single string or an array. All keyed values are flattened for matching.
	var obj map[string]StringOrStringSlice
	if err := json.Unmarshal(data, &obj); err == nil && len(obj) > 0 {
		// Sort keys so the flattened order is deterministic.
		keys := make([]string, 0, len(obj))
		for k := range obj {
			if _, ok := allowedPrincipalKeys[k]; !ok {
				return fmt.Errorf("unsupported Principal type %q (expected AWS, Service, Federated or CanonicalUser)", k)
			}
			keys = append(keys, k)
		}
		sort.Strings(keys)
		values := make([]string, 0, len(obj))
		for _, k := range keys {
			v := obj[k]
			items := v.Strings()
			if len(items) == 0 {
				return fmt.Errorf("Principal %q must list at least one value", k)
			}
			for _, item := range items {
				if item == "" {
					return fmt.Errorf("Principal %q must not contain empty values", k)
				}
			}
			values = append(values, items...)
		}
		p.values = values
		return nil
	}

	return fmt.Errorf(`Principal must be a string, an array of strings, or an object such as {"AWS": ["arn:..."]}`)
}

// MarshalJSON implements json.Marshaler for PolicyPrincipal.
func (p PolicyPrincipal) MarshalJSON() ([]byte, error) {
	// Echo the original JSON when we parsed it, so the shape is preserved.
	if len(p.raw) > 0 {
		return p.raw, nil
	}
	// Programmatically-constructed principals marshal as a bare string/array.
	if len(p.values) == 1 {
		return json.Marshal(p.values[0])
	}
	return json.Marshal(p.values)
}

// Strings returns the flattened principal values. Nil-safe for pointer receivers.
func (p *PolicyPrincipal) Strings() []string {
	if p == nil {
		return nil
	}
	return p.values
}

// NewPolicyPrincipalPtr builds a *PolicyPrincipal from flat values (no object wrapping).
func NewPolicyPrincipalPtr(values ...string) *PolicyPrincipal {
	return &PolicyPrincipal{values: values}
}

// ClonePolicyPrincipal deep-copies a *PolicyPrincipal (nil-safe).
func ClonePolicyPrincipal(p *PolicyPrincipal) *PolicyPrincipal {
	if p == nil {
		return nil
	}
	return &PolicyPrincipal{
		values: append([]string(nil), p.values...),
		raw:    append(json.RawMessage(nil), p.raw...),
	}
}

// PolicyConditions represents policy conditions with proper typing
type PolicyConditions map[string]map[string]StringOrStringSlice

// PolicyDocument represents an AWS S3 bucket policy document
type PolicyDocument struct {
	Version   string            `json:"Version"`
	Statement []PolicyStatement `json:"Statement"`
}

// UnmarshalJSON implements json.Unmarshaler for PolicyDocument
func (p *PolicyDocument) UnmarshalJSON(data []byte) error {
	type Alias PolicyDocument
	aux := &struct {
		Statement json.RawMessage `json:"Statement"`
		*Alias
	}{
		Alias: (*Alias)(p),
	}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	// Try unmarshaling as []PolicyStatement first
	var statements []PolicyStatement
	if err := json.Unmarshal(aux.Statement, &statements); err == nil {
		p.Statement = statements
		return nil
	}

	// Try unmarshaling as single PolicyStatement
	var statement PolicyStatement
	if err := json.Unmarshal(aux.Statement, &statement); err == nil {
		p.Statement = []PolicyStatement{statement}
		return nil
	}

	return fmt.Errorf("Statement must be an array or a single object")
}

// PolicyStatement represents a single policy statement
type PolicyStatement struct {
	Sid          string               `json:"Sid,omitempty"`
	Effect       PolicyEffect         `json:"Effect"`
	Principal    *PolicyPrincipal     `json:"Principal,omitempty"`
	NotPrincipal *PolicyPrincipal     `json:"NotPrincipal,omitempty"`
	Action       StringOrStringSlice  `json:"Action"`
	Resource     *StringOrStringSlice `json:"Resource,omitempty"`
	NotResource  *StringOrStringSlice `json:"NotResource,omitempty"`
	Condition    PolicyConditions     `json:"Condition,omitempty"`
}

// PolicyEffect represents Allow or Deny
type PolicyEffect string

const (
	PolicyEffectAllow PolicyEffect = "Allow"
	PolicyEffectDeny  PolicyEffect = "Deny"
)

// PolicyEvaluationArgs contains the arguments for policy evaluation
type PolicyEvaluationArgs struct {
	Action     string
	Resource   string
	Principal  string
	Conditions map[string][]string
	// ObjectEntry is the object's metadata from entry.Extended.
	// Used for evaluating conditions like s3:ExistingObjectTag/<tag-key>.
	// Tags are stored with s3_constants.AmzObjectTaggingPrefix (X-Amz-Tagging-) prefix.
	// Can be nil for bucket-level operations or when object doesn't exist.
	ObjectEntry map[string][]byte
	// Claims are JWT claims for jwt:* policy variables (can be nil)
	Claims map[string]interface{}
	// InheritedSSEAlgorithm is the canonical SSE algorithm ("AES256" or "aws:kms")
	// inherited from the CreateMultipartUpload request for UploadPart and
	// UploadPartCopy actions. The empty string means no SSE was used.
	InheritedSSEAlgorithm string
}

// PolicyCache for caching compiled policies
type PolicyCache struct {
	policies map[string]*CompiledPolicy
}

// CompiledPolicy represents a policy that has been compiled for efficient evaluation
type CompiledPolicy struct {
	Document   *PolicyDocument
	Statements []*CompiledStatement
}

// CompiledStatement represents a compiled policy statement
type CompiledStatement struct {
	Statement         *PolicyStatement
	ActionMatchers    []*wildcard.WildcardMatcher
	ResourceMatchers  []*wildcard.WildcardMatcher
	PrincipalMatchers []*wildcard.WildcardMatcher
	// Keep regex patterns for backward compatibility
	ActionPatterns    []*regexp.Regexp
	ResourcePatterns  []*regexp.Regexp
	PrincipalPatterns []*regexp.Regexp

	// dynamic patterns that require variable substitution before matching
	DynamicActionPatterns    []string
	DynamicResourcePatterns  []string
	DynamicPrincipalPatterns []string

	// NotPrincipal patterns (principal should NOT match these)
	NotPrincipalMatchers        []*wildcard.WildcardMatcher
	NotPrincipalPatterns        []*regexp.Regexp
	DynamicNotPrincipalPatterns []string

	// NotResource patterns (resource should NOT match these)
	NotResourcePatterns        []*regexp.Regexp
	NotResourceMatchers        []*wildcard.WildcardMatcher
	DynamicNotResourcePatterns []string
}

// NewPolicyCache creates a new policy cache
func NewPolicyCache() *PolicyCache {
	return &PolicyCache{
		policies: make(map[string]*CompiledPolicy),
	}
}

// ValidatePolicy validates a policy document
func ValidatePolicy(policyDoc *PolicyDocument) error {
	if policyDoc.Version != PolicyVersion2012_10_17 {
		return fmt.Errorf("unsupported policy version: %s", policyDoc.Version)
	}

	if len(policyDoc.Statement) == 0 {
		return fmt.Errorf("policy must contain at least one statement")
	}

	for i := range policyDoc.Statement {
		if err := validateStatement(&policyDoc.Statement[i]); err != nil {
			return fmt.Errorf("invalid statement %d: %v", i, err)
		}
	}

	return nil
}

// validateStatement validates a single policy statement
func validateStatement(stmt *PolicyStatement) error {
	if stmt.Effect != PolicyEffectAllow && stmt.Effect != PolicyEffectDeny {
		return fmt.Errorf("invalid effect: %s", stmt.Effect)
	}

	if len(stmt.Action.Strings()) == 0 {
		return fmt.Errorf("action is required")
	}

	if len(stmt.Resource.Strings()) == 0 && len(stmt.NotResource.Strings()) == 0 {
		return fmt.Errorf("statement must specify Resource or NotResource")
	}

	// AWS does not allow both Principal and NotPrincipal in the same statement.
	if stmt.Principal != nil && stmt.NotPrincipal != nil {
		return fmt.Errorf("statement cannot specify both Principal and NotPrincipal")
	}

	return nil
}

// ParsePolicy parses a policy JSON string
func ParsePolicy(policyJSON string) (*PolicyDocument, error) {
	var policy PolicyDocument
	if err := json.Unmarshal([]byte(policyJSON), &policy); err != nil {
		return nil, fmt.Errorf("failed to parse policy JSON: %w", err)
	}

	if err := ValidatePolicy(&policy); err != nil {
		return nil, fmt.Errorf("invalid policy: %w", err)
	}

	return &policy, nil
}

// CompilePolicy compiles a policy for efficient evaluation
func CompilePolicy(policy *PolicyDocument) (*CompiledPolicy, error) {
	compiled := &CompiledPolicy{
		Document:   policy,
		Statements: make([]*CompiledStatement, len(policy.Statement)),
	}

	for i := range policy.Statement {
		stmt := &policy.Statement[i]
		compiledStmt, err := compileStatement(stmt)
		if err != nil {
			return nil, fmt.Errorf("failed to compile statement %d: %v", i, err)
		}
		compiled.Statements[i] = compiledStmt
	}

	return compiled, nil
}

// compileStatement compiles a single policy statement
func compileStatement(stmt *PolicyStatement) (*CompiledStatement, error) {
	resStrings := slices.Clone(stmt.Resource.Strings())
	notResStrings := slices.Clone(stmt.NotResource.Strings())
	compiled := &CompiledStatement{
		Statement: &PolicyStatement{
			Sid:    stmt.Sid,
			Effect: stmt.Effect,
			Action: stmt.Action,
		},
	}

	// Deep clone Principal / NotPrincipal if present
	if stmt.Principal != nil {
		compiled.Statement.Principal = ClonePolicyPrincipal(stmt.Principal)
	}
	if stmt.NotPrincipal != nil {
		compiled.Statement.NotPrincipal = ClonePolicyPrincipal(stmt.NotPrincipal)
	}

	// Deep clone Resource/NotResource into the internal statement as well for completeness
	if stmt.Resource != nil {
		resourceClone := *stmt.Resource
		resourceClone.values = slices.Clone(stmt.Resource.values)
		compiled.Statement.Resource = &resourceClone
	}
	if stmt.NotResource != nil {
		notResourceClone := *stmt.NotResource
		notResourceClone.values = slices.Clone(stmt.NotResource.values)
		compiled.Statement.NotResource = &notResourceClone
	}
	compiled.Statement.Action.values = slices.Clone(stmt.Action.values)

	// Deep clone Condition map
	if stmt.Condition != nil {
		compiled.Statement.Condition = make(PolicyConditions)
		for k, v := range stmt.Condition {
			innerMap := make(map[string]StringOrStringSlice)
			for ik, iv := range v {
				innerMap[ik] = StringOrStringSlice{values: slices.Clone(iv.values)}
			}
			compiled.Statement.Condition[k] = innerMap
		}
	}

	// Compile action patterns and matchers
	for _, action := range stmt.Action.Strings() {
		if action == "" {
			continue
		}
		// Check for dynamic variables
		if PolicyVariableRegex.MatchString(action) {
			compiled.DynamicActionPatterns = append(compiled.DynamicActionPatterns, action)
			continue
		}

		pattern, err := compilePattern(action)
		if err != nil {
			return nil, fmt.Errorf("failed to compile action pattern %s: %v", action, err)
		}
		compiled.ActionPatterns = append(compiled.ActionPatterns, pattern)

		matcher, err := wildcard.NewWildcardMatcher(action)
		if err != nil {
			return nil, fmt.Errorf("failed to create action matcher %s: %v", action, err)
		}
		compiled.ActionMatchers = append(compiled.ActionMatchers, matcher)
	}

	// Compile resource patterns and matchers
	for _, resource := range resStrings {
		if resource == "" {
			continue
		}
		// Check for dynamic variables
		if PolicyVariableRegex.MatchString(resource) {
			compiled.DynamicResourcePatterns = append(compiled.DynamicResourcePatterns, resource)
			continue
		}

		pattern, err := compilePattern(resource)
		if err != nil {
			return nil, fmt.Errorf("failed to compile resource pattern %s: %v", resource, err)
		}
		compiled.ResourcePatterns = append(compiled.ResourcePatterns, pattern)

		matcher, err := wildcard.NewWildcardMatcher(resource)
		if err != nil {
			return nil, fmt.Errorf("failed to create resource matcher %s: %v", resource, err)
		}
		compiled.ResourceMatchers = append(compiled.ResourceMatchers, matcher)
	}

	// Compile principal patterns and matchers if present
	if stmt.Principal != nil && len(stmt.Principal.Strings()) > 0 {
		for _, principal := range stmt.Principal.Strings() {
			if principal == "" {
				continue
			}
			// Check for dynamic variables
			if PolicyVariableRegex.MatchString(principal) {
				compiled.DynamicPrincipalPatterns = append(compiled.DynamicPrincipalPatterns, principal)
				continue
			}

			pattern, err := compilePattern(principal)
			if err != nil {
				return nil, fmt.Errorf("failed to compile principal pattern %s: %v", principal, err)
			}
			compiled.PrincipalPatterns = append(compiled.PrincipalPatterns, pattern)

			matcher, err := wildcard.NewWildcardMatcher(principal)
			if err != nil {
				return nil, fmt.Errorf("failed to create principal matcher %s: %v", principal, err)
			}
			compiled.PrincipalMatchers = append(compiled.PrincipalMatchers, matcher)
		}
	}

	// Compile NotPrincipal patterns and matchers (principal should NOT match these)
	if stmt.NotPrincipal != nil && len(stmt.NotPrincipal.Strings()) > 0 {
		for _, notPrincipal := range stmt.NotPrincipal.Strings() {
			if notPrincipal == "" {
				continue
			}
			// Check for dynamic variables
			if PolicyVariableRegex.MatchString(notPrincipal) {
				compiled.DynamicNotPrincipalPatterns = append(compiled.DynamicNotPrincipalPatterns, notPrincipal)
				continue
			}

			pattern, err := compilePattern(notPrincipal)
			if err != nil {
				return nil, fmt.Errorf("failed to compile NotPrincipal pattern %s: %v", notPrincipal, err)
			}
			compiled.NotPrincipalPatterns = append(compiled.NotPrincipalPatterns, pattern)

			matcher, err := wildcard.NewWildcardMatcher(notPrincipal)
			if err != nil {
				return nil, fmt.Errorf("failed to create NotPrincipal matcher %s: %v", notPrincipal, err)
			}
			compiled.NotPrincipalMatchers = append(compiled.NotPrincipalMatchers, matcher)
		}
	}

	// Compile NotResource patterns (resource should NOT match these)
	if len(notResStrings) > 0 {
		for _, notResource := range notResStrings {
			if notResource == "" {
				continue
			}
			// Check for dynamic variables
			if PolicyVariableRegex.MatchString(notResource) {
				compiled.DynamicNotResourcePatterns = append(compiled.DynamicNotResourcePatterns, notResource)
				continue
			}

			pattern, err := compilePattern(notResource)
			if err != nil {
				return nil, fmt.Errorf("failed to compile NotResource pattern %s: %v", notResource, err)
			}
			compiled.NotResourcePatterns = append(compiled.NotResourcePatterns, pattern)

			matcher, err := wildcard.NewWildcardMatcher(notResource)
			if err != nil {
				return nil, fmt.Errorf("failed to create NotResource matcher %s: %v", notResource, err)
			}
			compiled.NotResourceMatchers = append(compiled.NotResourceMatchers, matcher)

			// Debug log
			// fmt.Printf("Compiled NotResource: %s\n", notResource)
		}
	}

	return compiled, nil
}

// compilePattern compiles a wildcard pattern to regex
func compilePattern(pattern string) (*regexp.Regexp, error) {
	return wildcard.CompileWildcardPattern(pattern)
}

// normalizeToStringSlice converts various types to string slice - kept for backward compatibility
func normalizeToStringSlice(value interface{}) []string {
	result, err := normalizeToStringSliceWithError(value)
	if err != nil {
		glog.Warningf("unexpected type for policy value: %T, error: %v", value, err)
		return []string{fmt.Sprintf("%v", value)}
	}
	return result
}

// normalizeToStringSliceWithError converts various types to string slice with proper error handling
func normalizeToStringSliceWithError(value interface{}) ([]string, error) {
	switch v := value.(type) {
	case string:
		return []string{v}, nil
	case []string:
		return v, nil
	case []interface{}:
		result := make([]string, len(v))
		for i, item := range v {
			result[i] = fmt.Sprintf("%v", item)
		}
		return result, nil
	case StringOrStringSlice:
		return v.Strings(), nil
	case *StringOrStringSlice:
		return v.Strings(), nil
	default:
		return nil, fmt.Errorf("unexpected type for policy value: %T", v)
	}
}

// GetBucketFromResource extracts bucket name from resource ARN
func GetBucketFromResource(resource string) string {
	// Handle ARN format: arn:aws:s3:::bucket-name/object-path
	if strings.HasPrefix(resource, "arn:aws:s3:::") {
		parts := strings.SplitN(resource[13:], "/", 2)
		return parts[0]
	}
	return ""
}

// MatchesAction checks if an action matches any of the compiled action matchers.
// It also implicitly grants multipart upload actions if s3:PutObject is allowed,
// since multipart upload is an implementation detail of putting objects.
func (cs *CompiledStatement) MatchesAction(action string) bool {
	var matchedAction, hasPutObjectPermission bool

	// Scan all matchers to check for both direct action match and s3:PutObject grant
	for _, matcher := range cs.ActionMatchers {
		if matcher.Match(action) {
			matchedAction = true
		}
		if !hasPutObjectPermission && matcher.Match("s3:PutObject") {
			hasPutObjectPermission = true
		}
	}

	// Return true if action matched directly or if multipart action with PutObject permission
	if matchedAction {
		return true
	}

	// Multipart upload operations are part of s3:PutObject permission
	// If s3:PutObject is allowed, implicitly allow multipart operations
	if hasPutObjectPermission && multipartActionSet[action] {
		return true
	}

	return false
}

// statementMayAllowAction reports whether a statement's actions can match the
// action, including the multipart operations that ride on s3:PutObject. It is
// looser than either evaluator on purpose: the IAM authorizer matches action
// names case-insensitively and a policy variable resolves per request, so a
// classifier that reads them strictly would miss a grant that is really there.
func statementMayAllowAction(actions []string, action string) bool {
	for _, pattern := range actions {
		if actionPatternMayMatch(pattern, action) {
			return true
		}
	}
	if !lowerMultipartActionSet[strings.ToLower(action)] {
		return false
	}
	for _, pattern := range actions {
		if actionPatternMayMatch(pattern, s3const.S3_ACTION_PUT_OBJECT) {
			return true
		}
	}
	return false
}

func actionPatternMayMatch(pattern, action string) bool {
	if PolicyVariableRegex.MatchString(pattern) {
		return true
	}
	if strings.EqualFold(pattern, action) {
		return true
	}
	return wildcard.MatchesWildcard(strings.ToLower(pattern), strings.ToLower(action))
}

// MatchesResource checks if a resource matches any of the compiled resource matchers
func (cs *CompiledStatement) MatchesResource(resource string) bool {
	for _, matcher := range cs.ResourceMatchers {
		if matcher.Match(resource) {
			return true
		}
	}
	return false
}

// MatchesPrincipal checks if a principal matches any of the compiled principal matchers
func (cs *CompiledStatement) MatchesPrincipal(principal string) bool {
	// If no principals specified, match all
	if len(cs.PrincipalMatchers) == 0 {
		return true
	}

	for _, matcher := range cs.PrincipalMatchers {
		if matcher.Match(principal) {
			return true
		}
	}
	return false
}

// matchesPrincipalSet reports whether the request principal matches any static
// matcher or dynamic (policy-variable) pattern in the given set.
func (cs *CompiledStatement) matchesPrincipalSet(args *PolicyEvaluationArgs, matchers []*wildcard.WildcardMatcher, dynamic []string) bool {
	for _, matcher := range matchers {
		if matcher.Match(args.Principal) {
			return true
		}
	}
	for _, pattern := range dynamic {
		substituted := SubstituteVariables(pattern, args.Conditions, args.Claims)
		if FastMatchesWildcard(substituted, args.Principal) {
			return true
		}
	}
	return false
}

// EvaluateStatement evaluates a compiled statement against the given arguments
func (cs *CompiledStatement) EvaluateStatement(args *PolicyEvaluationArgs) bool {
	// Check if action matches
	if !cs.MatchesAction(args.Action) {
		return false
	}

	// Check if resource matches
	if !cs.MatchesResource(args.Resource) {
		return false
	}

	// Principal / NotPrincipal (mutually exclusive per AWS): NotPrincipal makes
	// the statement apply to everyone EXCEPT the named principals; a plain
	// Principal requires a match. Both static matchers and dynamic
	// (policy-variable) patterns are honored.
	if len(cs.NotPrincipalMatchers) > 0 || len(cs.DynamicNotPrincipalPatterns) > 0 {
		if cs.matchesPrincipalSet(args, cs.NotPrincipalMatchers, cs.DynamicNotPrincipalPatterns) {
			return false
		}
	} else if len(cs.PrincipalMatchers) > 0 || len(cs.DynamicPrincipalPatterns) > 0 {
		if !cs.matchesPrincipalSet(args, cs.PrincipalMatchers, cs.DynamicPrincipalPatterns) {
			return false
		}
	}

	return true
}

// EvaluatePolicy evaluates a compiled policy against the given arguments
func (cp *CompiledPolicy) EvaluatePolicy(args *PolicyEvaluationArgs) (bool, PolicyEffect) {
	var explicitAllow, explicitDeny bool

	// Evaluate each statement
	for _, stmt := range cp.Statements {
		if stmt.EvaluateStatement(args) {
			if stmt.Statement.Effect == PolicyEffectAllow {
				explicitAllow = true
			} else if stmt.Statement.Effect == PolicyEffectDeny {
				explicitDeny = true
			}
		}
	}

	// AWS policy evaluation logic: explicit deny overrides allow
	if explicitDeny {
		return false, PolicyEffectDeny
	}
	if explicitAllow {
		return true, PolicyEffectAllow
	}

	// No matching statements - implicit deny
	return false, PolicyEffectDeny
}

// FastMatchesWildcard uses cached WildcardMatcher for performance
func FastMatchesWildcard(pattern, str string) bool {
	matcher, err := wildcard.GetCachedWildcardMatcher(pattern)
	if err != nil {
		glog.Errorf("Error getting cached WildcardMatcher for pattern %s: %v", pattern, err)
		// Fall back to the original implementation
		return wildcard.MatchesWildcard(pattern, str)
	}
	return matcher.Match(str)
}
