package iam

import (
	"crypto/rand"
	"crypto/sha1"
	"fmt"
	"math/big"
	"sort"

	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
)

// Hash computes a SHA1 hash of the input string.
func Hash(s *string) string {
	h := sha1.New()
	h.Write([]byte(*s))
	return fmt.Sprintf("%x", h.Sum(nil))
}

// UserArn builds an AWS-compatible IAM user ARN.
func UserArn(userName string) string {
	return fmt.Sprintf("arn:aws:iam::%s:user/%s", DefaultAccountID, userName)
}

// NewUser builds an iam.User for IAM API responses. The Arn must be a real ARN:
// the terraform aws provider (>= 6.41) reads a user back after creating it and
// blocks until GetUser returns a value that passes arn.IsARN, so an empty Arn
// leaves apply hanging until it times out.
func NewUser(userName string) iam.User {
	arn := UserArn(userName)
	path := "/"
	return iam.User{UserName: &userName, Arn: &arn, Path: &path}
}

// GenerateRandomString generates a cryptographically secure random string.
// Uses crypto/rand for security-sensitive credential generation.
func GenerateRandomString(length int, charset string) (string, error) {
	if length <= 0 {
		return "", fmt.Errorf("length must be positive, got %d", length)
	}
	if charset == "" {
		return "", fmt.Errorf("charset must not be empty")
	}
	b := make([]byte, length)
	for i := range b {
		n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
		if err != nil {
			return "", fmt.Errorf("failed to generate random index: %w", err)
		}
		b[i] = charset[n.Int64()]
	}
	return string(b), nil
}

// GenerateSecretAccessKey generates a new secret access key.
func GenerateSecretAccessKey() (string, error) {
	return GenerateRandomString(SecretAccessKeyLength, Charset)
}

// StringSlicesEqual compares two string slices for equality, ignoring order.
// This is used instead of reflect.DeepEqual to avoid order-dependent comparisons.
func StringSlicesEqual(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	// Make copies to avoid modifying the originals
	aCopy := make([]string, len(a))
	bCopy := make([]string, len(b))
	copy(aCopy, a)
	copy(bCopy, b)
	sort.Strings(aCopy)
	sort.Strings(bCopy)
	for i := range aCopy {
		if aCopy[i] != bCopy[i] {
			return false
		}
	}
	return true
}

// fineGrainedActionMap maps S3 IAM action names to internal S3 action constants.
// Supports both prefixed (e.g., "s3:DeleteObject") and unprefixed (e.g., "DeleteObject") formats.
// Populated in init() to avoid duplication of prefixed/unprefixed variants.
var fineGrainedActionMap = map[string]string{
	// Coarse-grained actions (populated statically)
	StatementActionAdmin:    s3_constants.ACTION_ADMIN,
	StatementActionWrite:    s3_constants.ACTION_WRITE,
	StatementActionWriteAcp: s3_constants.ACTION_WRITE_ACP,
	StatementActionRead:     s3_constants.ACTION_READ,
	StatementActionReadAcp:  s3_constants.ACTION_READ_ACP,
	StatementActionList:     s3_constants.ACTION_LIST,
	StatementActionTagging:  s3_constants.ACTION_TAGGING,
	StatementActionDelete:   s3_constants.ACTION_DELETE_BUCKET,
}

// baseS3ActionMap defines the base S3 actions that will be populated with both
// prefixed (s3:Action) and unprefixed (Action) variants in init().
var baseS3ActionMap = map[string]string{
	// Object operations
	"DeleteObject":        s3_constants.ACTION_WRITE,
	"PutObject":           s3_constants.ACTION_WRITE,
	"GetObject":           s3_constants.ACTION_READ,
	"DeleteObjectVersion": s3_constants.ACTION_WRITE,
	"GetObjectVersion":    s3_constants.ACTION_READ,
	// Tagging operations
	"GetObjectTagging":        s3_constants.ACTION_TAGGING,
	"GetObjectVersionTagging": s3_constants.ACTION_TAGGING,
	"PutObjectTagging":        s3_constants.ACTION_TAGGING,
	"DeleteObjectTagging":     s3_constants.ACTION_TAGGING,
	"GetBucketTagging":        s3_constants.ACTION_TAGGING,
	"PutBucketTagging":        s3_constants.ACTION_TAGGING,
	"DeleteBucketTagging":     s3_constants.ACTION_TAGGING,
	// ACL operations
	"PutObjectAcl":        s3_constants.ACTION_WRITE_ACP,
	"GetObjectAcl":        s3_constants.ACTION_READ_ACP,
	"GetObjectVersionAcl": s3_constants.ACTION_READ_ACP,
	"PutBucketAcl":        s3_constants.ACTION_WRITE_ACP,
	"GetBucketAcl":        s3_constants.ACTION_READ_ACP,
	// Bucket operations
	"DeleteBucket": s3_constants.ACTION_DELETE_BUCKET,
	// Bucket policy is permissions management: an explicit Allow in one skips
	// the IAM check, so it gets its own actions rather than folding into Write
	// (which any object writer holds) or Admin (which grants everything).
	"GetBucketPolicy":                  s3_constants.ACTION_READ,
	"PutBucketPolicy":                  s3_constants.ACTION_PUT_BUCKET_POLICY,
	"DeleteBucketPolicy":               s3_constants.ACTION_DELETE_BUCKET_POLICY,
	"ListBucket":                       s3_constants.ACTION_LIST,
	"ListBucketVersions":               s3_constants.ACTION_LIST,
	"ListAllMyBuckets":                 s3_constants.ACTION_LIST,
	"GetBucketLocation":                s3_constants.ACTION_READ,
	"GetBucketVersioning":              s3_constants.ACTION_READ,
	"PutBucketVersioning":              s3_constants.ACTION_WRITE,
	"GetBucketCors":                    s3_constants.ACTION_READ,
	"PutBucketCors":                    s3_constants.ACTION_WRITE,
	"DeleteBucketCors":                 s3_constants.ACTION_WRITE,
	"GetBucketNotification":            s3_constants.ACTION_READ,
	"PutBucketNotification":            s3_constants.ACTION_WRITE,
	"GetBucketObjectLockConfiguration": s3_constants.ACTION_READ,
	"PutBucketObjectLockConfiguration": s3_constants.ACTION_WRITE,
	// Multipart upload operations
	"CreateMultipartUpload":   s3_constants.ACTION_WRITE,
	"UploadPart":              s3_constants.ACTION_WRITE,
	"CompleteMultipartUpload": s3_constants.ACTION_WRITE,
	"AbortMultipartUpload":    s3_constants.ACTION_WRITE,
	"ListMultipartUploads":    s3_constants.ACTION_WRITE,
	"ListParts":               s3_constants.ACTION_WRITE,
	// Retention and legal hold operations
	"GetObjectRetention":        s3_constants.ACTION_READ,
	"PutObjectRetention":        s3_constants.ACTION_WRITE,
	"GetObjectLegalHold":        s3_constants.ACTION_READ,
	"PutObjectLegalHold":        s3_constants.ACTION_WRITE,
	"BypassGovernanceRetention": s3_constants.ACTION_WRITE,
}

func init() {
	// Populate both prefixed and unprefixed variants for all base S3 actions.
	// This avoids duplication and makes it easy to add new actions in one place.
	for action, constant := range baseS3ActionMap {
		fineGrainedActionMap[action] = constant       // unprefixed: "DeleteObject"
		fineGrainedActionMap["s3:"+action] = constant // prefixed: "s3:DeleteObject"
	}
}

// MapToStatementAction converts a policy statement action to an S3 action constant.
// It handles both coarse-grained statement actions (e.g., "Put*", "Get*") and
// fine-grained S3 actions (e.g., "s3:DeleteObject", "s3:PutObject") via exact lookup.
func MapToStatementAction(action string) string {
	if val, ok := fineGrainedActionMap[action]; ok {
		return val
	}
	return ""
}

// MapToIdentitiesAction converts an S3 action constant to a policy statement action.
func MapToIdentitiesAction(action string) string {
	switch action {
	case s3_constants.ACTION_ADMIN:
		return StatementActionAdmin
	case s3_constants.ACTION_WRITE:
		return StatementActionWrite
	case s3_constants.ACTION_WRITE_ACP:
		return StatementActionWriteAcp
	case s3_constants.ACTION_READ:
		return StatementActionRead
	case s3_constants.ACTION_READ_ACP:
		return StatementActionReadAcp
	case s3_constants.ACTION_LIST:
		return StatementActionList
	case s3_constants.ACTION_TAGGING:
		return StatementActionTagging
	case s3_constants.ACTION_DELETE_BUCKET:
		return StatementActionDelete
	case s3_constants.ACTION_PUT_BUCKET_POLICY:
		return "PutBucketPolicy"
	case s3_constants.ACTION_DELETE_BUCKET_POLICY:
		return "DeleteBucketPolicy"
	default:
		return ""
	}
}
