package policy_engine

import (
	"fmt"
	"net/http"
	"strings"

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

// Action represents an S3 action - this should match the type in auth_credentials.go
type Action string

// Identity represents a user identity - this should match the type in auth_credentials.go
type Identity interface {
	canDo(action Action, bucket string, objectKey string) bool
}

// PolicyBackedIAM provides policy-based access control with fallback to legacy IAM
type PolicyBackedIAM struct {
	policyEngine *PolicyEngine
	legacyIAM    LegacyIAM // Interface to delegate to existing IAM system
}

// LegacyIAM interface for delegating to existing IAM implementation
type LegacyIAM interface {
	authRequest(r *http.Request, action Action) (Identity, s3err.ErrorCode)
}

// NewPolicyBackedIAM creates a new policy-backed IAM system
func NewPolicyBackedIAM() *PolicyBackedIAM {
	return &PolicyBackedIAM{
		policyEngine: NewPolicyEngine(),
		legacyIAM:    nil, // Will be set when integrated with existing IAM
	}
}

// NewPolicyBackedIAMWithLegacy creates a new policy-backed IAM system with legacy IAM set
func NewPolicyBackedIAMWithLegacy(legacyIAM LegacyIAM) *PolicyBackedIAM {
	return &PolicyBackedIAM{
		policyEngine: NewPolicyEngine(),
		legacyIAM:    legacyIAM,
	}
}

// SetLegacyIAM sets the legacy IAM system for fallback
func (p *PolicyBackedIAM) SetLegacyIAM(legacyIAM LegacyIAM) {
	p.legacyIAM = legacyIAM
}

// SetBucketPolicy sets the policy for a bucket
func (p *PolicyBackedIAM) SetBucketPolicy(bucketName string, policyJSON string) error {
	return p.policyEngine.SetBucketPolicy(bucketName, policyJSON)
}

// GetBucketPolicy gets the policy for a bucket
func (p *PolicyBackedIAM) GetBucketPolicy(bucketName string) (*PolicyDocument, error) {
	return p.policyEngine.GetBucketPolicy(bucketName)
}

// DeleteBucketPolicy deletes the policy for a bucket
func (p *PolicyBackedIAM) DeleteBucketPolicy(bucketName string) error {
	return p.policyEngine.DeleteBucketPolicy(bucketName)
}

// CanDo checks if a principal can perform an action on a resource
func (p *PolicyBackedIAM) CanDo(action, bucketName, objectName, principal string, r *http.Request) bool {
	// If there's a bucket policy, evaluate it
	if p.policyEngine.HasPolicyForBucket(bucketName) {
		result := p.policyEngine.EvaluatePolicyForRequest(bucketName, objectName, action, principal, r)
		switch result {
		case PolicyResultAllow:
			return true
		case PolicyResultDeny:
			return false
		case PolicyResultIndeterminate:
			// Fall through to legacy system
		}
	}

	// No bucket policy or indeterminate result, use legacy conversion
	return p.evaluateLegacyAction(action, bucketName, objectName, principal)
}

// evaluateLegacyAction evaluates actions using legacy identity-based rules
func (p *PolicyBackedIAM) evaluateLegacyAction(action, bucketName, objectName, principal string) bool {
	// If we have a legacy IAM system to delegate to, use it
	if p.legacyIAM != nil {
		// Create a dummy request for legacy evaluation
		// In real implementation, this would use the actual request
		r := &http.Request{
			Header: make(http.Header),
		}

		// Convert the action string to Action type
		legacyAction := Action(action)

		// Use legacy IAM to check permission
		identity, errCode := p.legacyIAM.authRequest(r, legacyAction)
		if errCode != s3err.ErrNone {
			return false
		}

		// If we have an identity, check if it can perform the action
		if identity != nil {
			return identity.canDo(legacyAction, bucketName, objectName)
		}
	}

	// No legacy IAM available, convert to policy and evaluate
	return p.evaluateUsingPolicyConversion(action, bucketName, objectName, principal)
}

// evaluateUsingPolicyConversion converts legacy action to policy and evaluates
func (p *PolicyBackedIAM) evaluateUsingPolicyConversion(action, bucketName, objectName, principal string) bool {
	// For now, use a conservative approach for legacy actions
	// In a real implementation, this would integrate with the existing identity system
	glog.V(2).Infof("Legacy action evaluation for %s on %s/%s by %s", action, bucketName, objectName, principal)

	// Return false to maintain security until proper legacy integration is implemented
	// This ensures no unintended access is granted
	return false
}

// ConvertIdentityToPolicy converts a legacy identity action to an AWS policy
func ConvertIdentityToPolicy(identityActions []string, bucketName string) (*PolicyDocument, error) {
	statements := make([]PolicyStatement, 0)

	for _, action := range identityActions {
		stmt, err := convertSingleAction(action, bucketName)
		if err != nil {
			glog.Warningf("Failed to convert action %s: %v", action, err)
			continue
		}
		if stmt != nil {
			statements = append(statements, *stmt)
		}
	}

	if len(statements) == 0 {
		return nil, fmt.Errorf("no valid statements generated")
	}

	return &PolicyDocument{
		Version:   PolicyVersion2012_10_17,
		Statement: statements,
	}, nil
}

// convertSingleAction converts a single legacy action to a policy statement
func convertSingleAction(action, bucketName string) (*PolicyStatement, error) {
	parts := strings.Split(action, ":")
	if len(parts) != 2 {
		return nil, fmt.Errorf("invalid action format: %s", action)
	}

	actionType := parts[0]
	resourcePattern := parts[1]

	var s3Actions []string
	var resources []string

	switch actionType {
	case "Read":
		s3Actions = []string{"s3:GetObject", "s3:GetObjectVersion", "s3:ListBucket"}
		if strings.HasSuffix(resourcePattern, "/*") {
			// Object-level read access
			bucket := strings.TrimSuffix(resourcePattern, "/*")
			resources = []string{
				fmt.Sprintf("arn:aws:s3:::%s", bucket),
				fmt.Sprintf("arn:aws:s3:::%s/*", bucket),
			}
		} else {
			// Bucket-level read access
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s", resourcePattern)}
		}

	case "Write":
		s3Actions = []string{"s3:PutObject", "s3:DeleteObject", "s3:PutObjectAcl"}
		if strings.HasSuffix(resourcePattern, "/*") {
			// Object-level write access
			bucket := strings.TrimSuffix(resourcePattern, "/*")
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", bucket)}
		} else {
			// Bucket-level write access
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s", resourcePattern)}
		}

	case "Admin":
		s3Actions = []string{"s3:*"}
		resources = []string{
			fmt.Sprintf("arn:aws:s3:::%s", resourcePattern),
			fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern),
		}

	case "List":
		s3Actions = []string{"s3:ListBucket", "s3:ListBucketVersions"}
		if strings.HasSuffix(resourcePattern, "/*") {
			// Object-level list access - extract bucket from "bucket/prefix/*" pattern
			patternWithoutWildcard := strings.TrimSuffix(resourcePattern, "/*")
			parts := strings.SplitN(patternWithoutWildcard, "/", 2)
			bucket := parts[0]
			resources = []string{
				fmt.Sprintf("arn:aws:s3:::%s", bucket),
				fmt.Sprintf("arn:aws:s3:::%s/*", bucket),
			}
		} else {
			// Bucket-level list access
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s", resourcePattern)}
		}

	case "Tagging":
		s3Actions = []string{"s3:GetObjectTagging", "s3:PutObjectTagging", "s3:DeleteObjectTagging"}
		resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern)}

	case "BypassGovernanceRetention":
		s3Actions = []string{"s3:BypassGovernanceRetention"}
		if strings.HasSuffix(resourcePattern, "/*") {
			// Object-level bypass governance access
			bucket := strings.TrimSuffix(resourcePattern, "/*")
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", bucket)}
		} else {
			// Bucket-level bypass governance access
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern)}
		}

	case "GetObjectRetention":
		s3Actions = []string{"s3:GetObjectRetention"}
		if strings.HasSuffix(resourcePattern, "/*") {
			bucket := strings.TrimSuffix(resourcePattern, "/*")
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", bucket)}
		} else {
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern)}
		}

	case "PutObjectRetention":
		s3Actions = []string{"s3:PutObjectRetention"}
		if strings.HasSuffix(resourcePattern, "/*") {
			bucket := strings.TrimSuffix(resourcePattern, "/*")
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", bucket)}
		} else {
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern)}
		}

	case "GetObjectLegalHold":
		s3Actions = []string{"s3:GetObjectLegalHold"}
		if strings.HasSuffix(resourcePattern, "/*") {
			bucket := strings.TrimSuffix(resourcePattern, "/*")
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", bucket)}
		} else {
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern)}
		}

	case "PutObjectLegalHold":
		s3Actions = []string{"s3:PutObjectLegalHold"}
		if strings.HasSuffix(resourcePattern, "/*") {
			bucket := strings.TrimSuffix(resourcePattern, "/*")
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", bucket)}
		} else {
			resources = []string{fmt.Sprintf("arn:aws:s3:::%s/*", resourcePattern)}
		}

	case "GetBucketObjectLockConfiguration":
		s3Actions = []string{"s3:GetBucketObjectLockConfiguration"}
		resources = []string{fmt.Sprintf("arn:aws:s3:::%s", resourcePattern)}

	case "PutBucketObjectLockConfiguration":
		s3Actions = []string{"s3:PutBucketObjectLockConfiguration"}
		resources = []string{fmt.Sprintf("arn:aws:s3:::%s", resourcePattern)}

	default:
		return nil, fmt.Errorf("unknown action type: %s", actionType)
	}

	return &PolicyStatement{
		Effect:   PolicyEffectAllow,
		Action:   NewStringOrStringSlice(s3Actions...),
		Resource: NewStringOrStringSlice(resources...),
	}, nil
}

// GetActionMappings returns the mapping of legacy actions to S3 actions
func GetActionMappings() map[string][]string {
	return map[string][]string{
		"Read": {
			"s3:GetObject",
			"s3:GetObjectVersion",
			"s3:GetObjectAcl",
			"s3:GetObjectVersionAcl",
			"s3:GetObjectTagging",
			"s3:GetObjectVersionTagging",
			"s3:ListBucket",
			"s3:ListBucketVersions",
			"s3:GetBucketLocation",
			"s3:GetBucketVersioning",
			"s3:GetBucketAcl",
			"s3:GetBucketCors",
			"s3:GetBucketTagging",
			"s3:GetBucketNotification",
		},
		"Write": {
			"s3:PutObject",
			"s3:PutObjectAcl",
			"s3:PutObjectTagging",
			"s3:DeleteObject",
			"s3:DeleteObjectVersion",
			"s3:DeleteObjectTagging",
			"s3:AbortMultipartUpload",
			"s3:ListMultipartUploads",
			"s3:ListParts",
			"s3:PutBucketAcl",
			"s3:PutBucketCors",
			"s3:PutBucketTagging",
			"s3:PutBucketNotification",
			"s3:PutBucketVersioning",
			"s3:DeleteBucketTagging",
			"s3:DeleteBucketCors",
		},
		"Admin": {
			"s3:*",
		},
		"List": {
			"s3:ListBucket",
			"s3:ListBucketVersions",
			"s3:ListAllMyBuckets",
		},
		"Tagging": {
			"s3:GetObjectTagging",
			"s3:PutObjectTagging",
			"s3:DeleteObjectTagging",
			"s3:GetBucketTagging",
			"s3:PutBucketTagging",
			"s3:DeleteBucketTagging",
		},
		"BypassGovernanceRetention": {
			"s3:BypassGovernanceRetention",
		},
		"GetObjectRetention": {
			"s3:GetObjectRetention",
		},
		"PutObjectRetention": {
			"s3:PutObjectRetention",
		},
		"GetObjectLegalHold": {
			"s3:GetObjectLegalHold",
		},
		"PutObjectLegalHold": {
			"s3:PutObjectLegalHold",
		},
		"GetBucketObjectLockConfiguration": {
			"s3:GetBucketObjectLockConfiguration",
		},
		"PutBucketObjectLockConfiguration": {
			"s3:PutBucketObjectLockConfiguration",
		},
	}
}

// ValidateActionMapping validates that a legacy action can be mapped to S3 actions
func ValidateActionMapping(action string) error {
	mappings := GetActionMappings()

	parts := strings.Split(action, ":")
	if len(parts) != 2 {
		return fmt.Errorf("invalid action format: %s, expected format: 'ActionType:Resource'", action)
	}

	actionType := parts[0]
	resource := parts[1]

	if _, exists := mappings[actionType]; !exists {
		return fmt.Errorf("unknown action type: %s", actionType)
	}

	if resource == "" {
		return fmt.Errorf("resource cannot be empty")
	}

	return nil
}

// ConvertLegacyActions converts an array of legacy actions to S3 actions
func ConvertLegacyActions(legacyActions []string) ([]string, error) {
	mappings := GetActionMappings()
	s3Actions := make([]string, 0)

	for _, legacyAction := range legacyActions {
		if err := ValidateActionMapping(legacyAction); err != nil {
			return nil, err
		}

		parts := strings.Split(legacyAction, ":")
		actionType := parts[0]

		if actionType == "Admin" {
			// Admin gives all permissions, so we can just return s3:*
			return []string{"s3:*"}, nil
		}

		if mapped, exists := mappings[actionType]; exists {
			s3Actions = append(s3Actions, mapped...)
		}
	}

	// Remove duplicates
	uniqueActions := make([]string, 0)
	seen := make(map[string]bool)
	for _, action := range s3Actions {
		if !seen[action] {
			uniqueActions = append(uniqueActions, action)
			seen[action] = true
		}
	}

	return uniqueActions, nil
}

// GetResourcesFromLegacyAction extracts resources from a legacy action
func GetResourcesFromLegacyAction(legacyAction string) ([]string, error) {
	parts := strings.Split(legacyAction, ":")
	if len(parts) != 2 {
		return nil, fmt.Errorf("invalid action format: %s", legacyAction)
	}

	resourcePattern := parts[1]
	resources := make([]string, 0)

	if strings.HasSuffix(resourcePattern, "/*") {
		// Object-level access
		bucket := strings.TrimSuffix(resourcePattern, "/*")
		resources = append(resources, fmt.Sprintf("arn:aws:s3:::%s", bucket))
		resources = append(resources, fmt.Sprintf("arn:aws:s3:::%s/*", bucket))
	} else {
		// Bucket-level access
		resources = append(resources, fmt.Sprintf("arn:aws:s3:::%s", resourcePattern))
	}

	return resources, nil
}

// CreatePolicyFromLegacyIdentity creates a policy document from legacy identity actions
func CreatePolicyFromLegacyIdentity(identityName string, actions []string) (*PolicyDocument, error) {
	statements := make([]PolicyStatement, 0)

	// Group actions by resource pattern
	resourceActions := make(map[string][]string)

	for _, action := range actions {
		parts := strings.Split(action, ":")
		if len(parts) != 2 {
			continue
		}

		resourcePattern := parts[1]
		actionType := parts[0]

		if _, exists := resourceActions[resourcePattern]; !exists {
			resourceActions[resourcePattern] = make([]string, 0)
		}
		resourceActions[resourcePattern] = append(resourceActions[resourcePattern], actionType)
	}

	// Create statements for each resource pattern
	for resourcePattern, actionTypes := range resourceActions {
		s3Actions := make([]string, 0)

		for _, actionType := range actionTypes {
			if actionType == "Admin" {
				s3Actions = []string{"s3:*"}
				break
			}

			if mapped, exists := GetActionMappings()[actionType]; exists {
				s3Actions = append(s3Actions, mapped...)
			}
		}

		resources, err := GetResourcesFromLegacyAction(fmt.Sprintf("dummy:%s", resourcePattern))
		if err != nil {
			continue
		}

		statement := PolicyStatement{
			Sid:      fmt.Sprintf("%s-%s", identityName, strings.ReplaceAll(resourcePattern, "/", "-")),
			Effect:   PolicyEffectAllow,
			Action:   NewStringOrStringSlice(s3Actions...),
			Resource: NewStringOrStringSlice(resources...),
		}

		statements = append(statements, statement)
	}

	if len(statements) == 0 {
		return nil, fmt.Errorf("no valid statements generated for identity %s", identityName)
	}

	return &PolicyDocument{
		Version:   PolicyVersion2012_10_17,
		Statement: statements,
	}, nil
}

// HasPolicyForBucket checks if a bucket has a policy
func (p *PolicyBackedIAM) HasPolicyForBucket(bucketName string) bool {
	return p.policyEngine.HasPolicyForBucket(bucketName)
}

// GetPolicyEngine returns the underlying policy engine
func (p *PolicyBackedIAM) GetPolicyEngine() *PolicyEngine {
	return p.policyEngine
}
