package policy_engine

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"
	"time"

	"github.com/seaweedfs/seaweedfs/weed/glog"
)

// 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"
)

// 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
func (s StringOrStringSlice) Strings() []string {
	return s.values
}

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

// 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"`
}

// PolicyStatement represents a single policy statement
type PolicyStatement struct {
	Sid       string               `json:"Sid,omitempty"`
	Effect    PolicyEffect         `json:"Effect"`
	Principal *StringOrStringSlice `json:"Principal,omitempty"`
	Action    StringOrStringSlice  `json:"Action"`
	Resource  StringOrStringSlice  `json:"Resource"`
	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
}

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

// 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    []*WildcardMatcher
	ResourceMatchers  []*WildcardMatcher
	PrincipalMatchers []*WildcardMatcher
	// Keep regex patterns for backward compatibility
	ActionPatterns    []*regexp.Regexp
	ResourcePatterns  []*regexp.Regexp
	PrincipalPatterns []*regexp.Regexp
}

// 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, stmt := range policyDoc.Statement {
		if err := validateStatement(&stmt); 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 {
		return fmt.Errorf("resource is required")
	}

	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, stmt := range policy.Statement {
		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) {
	compiled := &CompiledStatement{
		Statement: stmt,
	}

	// Compile action patterns and matchers
	for _, action := range stmt.Action.Strings() {
		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 := 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 stmt.Resource.Strings() {
		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 := 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() {
			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 := NewWildcardMatcher(principal)
			if err != nil {
				return nil, fmt.Errorf("failed to create principal matcher %s: %v", principal, err)
			}
			compiled.PrincipalMatchers = append(compiled.PrincipalMatchers, matcher)
		}
	}

	return compiled, nil
}

// compilePattern compiles a wildcard pattern to regex
func compilePattern(pattern string) (*regexp.Regexp, error) {
	return 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
	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 ""
}

// IsObjectResource checks if resource refers to objects
func IsObjectResource(resource string) bool {
	return strings.Contains(resource, "/")
}

// S3Actions contains common S3 actions
var S3Actions = map[string]string{
	"GetObject":                        "s3:GetObject",
	"PutObject":                        "s3:PutObject",
	"DeleteObject":                     "s3:DeleteObject",
	"GetObjectVersion":                 "s3:GetObjectVersion",
	"DeleteObjectVersion":              "s3:DeleteObjectVersion",
	"ListBucket":                       "s3:ListBucket",
	"ListBucketVersions":               "s3:ListBucketVersions",
	"GetBucketLocation":                "s3:GetBucketLocation",
	"GetBucketVersioning":              "s3:GetBucketVersioning",
	"PutBucketVersioning":              "s3:PutBucketVersioning",
	"GetBucketAcl":                     "s3:GetBucketAcl",
	"PutBucketAcl":                     "s3:PutBucketAcl",
	"GetObjectAcl":                     "s3:GetObjectAcl",
	"PutObjectAcl":                     "s3:PutObjectAcl",
	"GetBucketPolicy":                  "s3:GetBucketPolicy",
	"PutBucketPolicy":                  "s3:PutBucketPolicy",
	"DeleteBucketPolicy":               "s3:DeleteBucketPolicy",
	"GetBucketCors":                    "s3:GetBucketCors",
	"PutBucketCors":                    "s3:PutBucketCors",
	"DeleteBucketCors":                 "s3:DeleteBucketCors",
	"GetBucketNotification":            "s3:GetBucketNotification",
	"PutBucketNotification":            "s3:PutBucketNotification",
	"GetBucketTagging":                 "s3:GetBucketTagging",
	"PutBucketTagging":                 "s3:PutBucketTagging",
	"DeleteBucketTagging":              "s3:DeleteBucketTagging",
	"GetObjectTagging":                 "s3:GetObjectTagging",
	"PutObjectTagging":                 "s3:PutObjectTagging",
	"DeleteObjectTagging":              "s3:DeleteObjectTagging",
	"ListMultipartUploads":             "s3:ListMultipartUploads",
	"AbortMultipartUpload":             "s3:AbortMultipartUpload",
	"ListParts":                        "s3:ListParts",
	"GetObjectRetention":               "s3:GetObjectRetention",
	"PutObjectRetention":               "s3:PutObjectRetention",
	"GetObjectLegalHold":               "s3:GetObjectLegalHold",
	"PutObjectLegalHold":               "s3:PutObjectLegalHold",
	"GetBucketObjectLockConfiguration": "s3:GetBucketObjectLockConfiguration",
	"PutBucketObjectLockConfiguration": "s3:PutBucketObjectLockConfiguration",
	"BypassGovernanceRetention":        "s3:BypassGovernanceRetention",
}

// MatchesAction checks if an action matches any of the compiled action matchers
func (cs *CompiledStatement) MatchesAction(action string) bool {
	for _, matcher := range cs.ActionMatchers {
		if matcher.Match(action) {
			return true
		}
	}
	return false
}

// 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
}

// 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
	}

	// Check if principal matches
	if !cs.MatchesPrincipal(args.Principal) {
		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 := GetCachedWildcardMatcher(pattern)
	if err != nil {
		glog.Errorf("Error getting cached WildcardMatcher for pattern %s: %v", pattern, err)
		// Fall back to the original implementation
		return MatchesWildcard(pattern, str)
	}
	return matcher.Match(str)
}
