package dash

import (
	"context"
	"crypto/rand"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/seaweedfs/seaweedfs/weed/credential"
	"github.com/seaweedfs/seaweedfs/weed/iam"
	"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
)

var (
	ErrAccessKeyInUse = errors.New("access key already in use")
	ErrUserNotFound   = errors.New("user not found")
	ErrInvalidInput   = errors.New("invalid input")
)

// CreateObjectStoreUser creates a new user using the credential manager
func (s *AdminServer) CreateObjectStoreUser(req CreateUserRequest) (*ObjectStoreUser, error) {
	if s.credentialManager == nil {
		return nil, fmt.Errorf("credential manager not available")
	}

	ctx := context.Background()

	// Create new identity
	newIdentity := &iam_pb.Identity{
		Name:        req.Username,
		Actions:     req.Actions,
		PolicyNames: req.PolicyNames,
	}

	// Add account if email is provided
	if req.Email != "" {
		newIdentity.Account = &iam_pb.Account{
			Id:           generateAccountId(),
			DisplayName:  req.Username,
			EmailAddress: req.Email,
		}
	}

	// Generate access key if requested
	var accessKey, secretKey string
	if req.GenerateKey {
		accessKey = generateAccessKey()
		secretKey = generateSecretKey()
		newIdentity.Credentials = []*iam_pb.Credential{
			{
				AccessKey: accessKey,
				SecretKey: secretKey,
			},
		}
	}

	// Create user using credential manager
	err := s.credentialManager.CreateUser(ctx, newIdentity)
	if err != nil {
		if err == credential.ErrUserAlreadyExists {
			return nil, fmt.Errorf("user %s already exists", req.Username)
		}
		return nil, fmt.Errorf("failed to create user: %w", err)
	}

	// Return created user
	user := &ObjectStoreUser{
		Username:    req.Username,
		Email:       req.Email,
		AccessKey:   accessKey,
		SecretKey:   secretKey,
		Permissions: req.Actions,
		PolicyNames: req.PolicyNames,
	}

	return user, nil
}

// UpdateObjectStoreUser updates an existing user
func (s *AdminServer) UpdateObjectStoreUser(username string, req UpdateUserRequest) (*ObjectStoreUser, error) {
	if s.credentialManager == nil {
		return nil, fmt.Errorf("credential manager not available")
	}

	ctx := context.Background()

	// Get existing user
	identity, err := s.credentialManager.GetUser(ctx, username)
	if err != nil {
		if err == credential.ErrUserNotFound {
			return nil, fmt.Errorf("user %s not found", username)
		}
		return nil, fmt.Errorf("failed to get user: %w", err)
	}

	// Create updated identity
	updatedIdentity := &iam_pb.Identity{
		Name:        identity.Name,
		Account:     identity.Account,
		Credentials: identity.Credentials,
		Actions:     identity.Actions,
		PolicyNames: identity.PolicyNames,
	}

	// Update actions if provided
	if req.Actions != nil {
		updatedIdentity.Actions = req.Actions
	}
	// Always update policy names when present in request (even if empty to allow clearing)
	if req.PolicyNames != nil {
		updatedIdentity.PolicyNames = req.PolicyNames
	}

	// Update email if provided
	if req.Email != "" {
		if updatedIdentity.Account == nil {
			updatedIdentity.Account = &iam_pb.Account{
				Id:          generateAccountId(),
				DisplayName: username,
			}
		}
		updatedIdentity.Account.EmailAddress = req.Email
	}

	// Update user using credential manager
	err = s.credentialManager.UpdateUser(ctx, username, updatedIdentity)
	if err != nil {
		return nil, fmt.Errorf("failed to update user: %w", err)
	}

	// Return updated user
	user := &ObjectStoreUser{
		Username:    username,
		Email:       req.Email,
		Permissions: updatedIdentity.Actions,
		PolicyNames: updatedIdentity.PolicyNames,
	}

	// Get first access key for display
	if len(updatedIdentity.Credentials) > 0 {
		user.AccessKey = updatedIdentity.Credentials[0].AccessKey
		user.SecretKey = updatedIdentity.Credentials[0].SecretKey
	}

	return user, nil
}

// DeleteObjectStoreUser deletes a user using the credential manager
func (s *AdminServer) DeleteObjectStoreUser(username string) error {
	if s.credentialManager == nil {
		return fmt.Errorf("credential manager not available")
	}

	ctx := context.Background()

	// Delete user using credential manager
	err := s.credentialManager.DeleteUser(ctx, username)
	if err != nil {
		if err == credential.ErrUserNotFound {
			return fmt.Errorf("user %s not found", username)
		}
		return fmt.Errorf("failed to delete user: %w", err)
	}

	return nil
}

// GetObjectStoreUserDetails returns detailed information about a user
func (s *AdminServer) GetObjectStoreUserDetails(username string) (*UserDetails, error) {
	if s.credentialManager == nil {
		return nil, fmt.Errorf("credential manager not available")
	}

	ctx := context.Background()

	// Get user using credential manager (resolves static users via filer gRPC)
	identity, err := s.credentialManager.GetUser(ctx, username)
	if err != nil {
		if err == credential.ErrUserNotFound {
			return nil, fmt.Errorf("user %s not found", username)
		}
		return nil, fmt.Errorf("failed to get user: %w", err)
	}

	details := &UserDetails{
		Username:    username,
		Actions:     identity.Actions,
		PolicyNames: identity.PolicyNames,
	}

	// Set email from account if available
	if identity.Account != nil {
		details.Email = identity.Account.EmailAddress
	}

	// Look up groups the user belongs to
	groupNames, err := s.credentialManager.ListGroups(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to list groups: %w", err)
	}
	for _, gName := range groupNames {
		g, err := s.credentialManager.GetGroup(ctx, gName)
		if err != nil {
			return nil, fmt.Errorf("failed to get group %s: %w", gName, err)
		}
		for _, member := range g.Members {
			if member == username {
				details.Groups = append(details.Groups, gName)
				break
			}
		}
	}

	// Convert credentials to access key info
	for _, cred := range identity.Credentials {
		details.AccessKeys = append(details.AccessKeys, AccessKeyInfo{
			AccessKey: cred.AccessKey,
			SecretKey: cred.SecretKey,
			Status:    cred.Status,
			CreatedAt: time.Now().AddDate(0, -1, 0), // Mock creation date
		})
	}

	return details, nil
}

// CreateAccessKey creates a new access key for a user
func (s *AdminServer) CreateAccessKey(username string, req *CreateAccessKeyRequest) (*AccessKeyInfo, error) {
	if s.credentialManager == nil {
		return nil, fmt.Errorf("credential manager not available")
	}

	ctx := context.Background()

	// Check if user exists
	_, err := s.credentialManager.GetUser(ctx, username)
	if err != nil {
		if err == credential.ErrUserNotFound {
			return nil, fmt.Errorf("user %s: %w", username, ErrUserNotFound)
		}
		return nil, fmt.Errorf("failed to get user: %w", err)
	}

	if req == nil {
		req = &CreateAccessKeyRequest{}
	}

	// Validate provided keys (shared with the IAM API and embedded IAM paths).
	if req.AccessKey != "" {
		if err := iam.ValidateCallerSuppliedAccessKeyId(req.AccessKey); err != nil {
			return nil, fmt.Errorf("%s: %w", err.Error(), ErrInvalidInput)
		}
	}
	if req.SecretKey != "" {
		if err := iam.ValidateCallerSuppliedSecretAccessKey(req.SecretKey); err != nil {
			return nil, fmt.Errorf("%s: %w", err.Error(), ErrInvalidInput)
		}
	}
	// Enforce the both-or-none rule to match the IAM API and embedded IAM
	// paths — silently generating the missing half lets a caller end up
	// with a credential they did not fully choose.
	if (req.AccessKey != "") != (req.SecretKey != "") {
		return nil, fmt.Errorf("access key and secret key must be supplied together: %w", ErrInvalidInput)
	}

	// Use provided keys or generate new ones
	accessKey := req.AccessKey
	secretKey := req.SecretKey
	if accessKey == "" {
		accessKey = generateAccessKey()
		secretKey = generateSecretKey()
	}

	// Verify access key is globally unique
	existingUser, err := s.credentialManager.GetUserByAccessKey(ctx, accessKey)
	if existingUser != nil {
		return nil, ErrAccessKeyInUse
	}
	if err != nil && !errors.Is(err, credential.ErrAccessKeyNotFound) && !isNotFoundError(err) {
		return nil, fmt.Errorf("failed to check access key uniqueness: %w", err)
	}

	credential := &iam_pb.Credential{
		AccessKey: accessKey,
		SecretKey: secretKey,
		Status:    AccessKeyStatusActive,
	}

	// Create access key using credential manager
	err = s.credentialManager.CreateAccessKey(ctx, username, credential)
	if err != nil {
		return nil, fmt.Errorf("failed to create access key: %w", err)
	}

	return &AccessKeyInfo{
		AccessKey: accessKey,
		SecretKey: secretKey,
		Status:    AccessKeyStatusActive,
		CreatedAt: time.Now(),
	}, nil
}

// DeleteAccessKey deletes an access key for a user
func (s *AdminServer) DeleteAccessKey(username, accessKeyId string) error {
	if s.credentialManager == nil {
		return fmt.Errorf("credential manager not available")
	}

	ctx := context.Background()

	// Delete access key using credential manager
	err := s.credentialManager.DeleteAccessKey(ctx, username, accessKeyId)
	if err != nil {
		if err == credential.ErrUserNotFound {
			return fmt.Errorf("user %s not found", username)
		}
		if err == credential.ErrAccessKeyNotFound {
			return fmt.Errorf("access key %s not found for user %s", accessKeyId, username)
		}
		return fmt.Errorf("failed to delete access key: %w", err)
	}

	return nil
}

// UpdateAccessKeyStatus updates the status of an access key for a user
func (s *AdminServer) UpdateAccessKeyStatus(username, accessKeyId, status string) error {
	if s.credentialManager == nil {
		return fmt.Errorf("credential manager not available")
	}

	// Validate status against allowed values
	if status != AccessKeyStatusActive && status != AccessKeyStatusInactive {
		return fmt.Errorf("invalid status '%s': must be '%s' or '%s'", status, AccessKeyStatusActive, AccessKeyStatusInactive)
	}

	ctx := context.Background()

	// Get user using credential manager
	identity, err := s.credentialManager.GetUser(ctx, username)
	if err != nil {
		if err == credential.ErrUserNotFound {
			return fmt.Errorf("user %s not found", username)
		}
		return fmt.Errorf("failed to get user: %w", err)
	}

	// Find and update the access key status
	found := false
	for _, cred := range identity.Credentials {
		if cred.AccessKey == accessKeyId {
			cred.Status = status
			found = true
			break
		}
	}

	if !found {
		return fmt.Errorf("access key %s not found for user %s", accessKeyId, username)
	}

	// Update user using credential manager
	err = s.credentialManager.UpdateUser(ctx, username, identity)
	if err != nil {
		return fmt.Errorf("failed to update user access key status: %w", err)
	}

	return nil
}

// GetUserPolicies returns the policies for a user (actions)
func (s *AdminServer) GetUserPolicies(username string) ([]string, error) {
	if s.credentialManager == nil {
		return nil, fmt.Errorf("credential manager not available")
	}

	ctx := context.Background()

	// Get user using credential manager
	identity, err := s.credentialManager.GetUser(ctx, username)
	if err != nil {
		if err == credential.ErrUserNotFound {
			return nil, fmt.Errorf("user %s not found", username)
		}
		return nil, fmt.Errorf("failed to get user: %w", err)
	}

	return identity.Actions, nil
}

// UpdateUserPolicies updates the policies (actions) for a user
func (s *AdminServer) UpdateUserPolicies(username string, actions []string) error {
	if s.credentialManager == nil {
		return fmt.Errorf("credential manager not available")
	}

	ctx := context.Background()

	// Get existing user
	identity, err := s.credentialManager.GetUser(ctx, username)
	if err != nil {
		if err == credential.ErrUserNotFound {
			return fmt.Errorf("user %s not found", username)
		}
		return fmt.Errorf("failed to get user: %w", err)
	}

	// Create updated identity with new actions
	updatedIdentity := &iam_pb.Identity{
		Name:        identity.Name,
		Account:     identity.Account,
		Credentials: identity.Credentials,
		Actions:     actions,
		PolicyNames: identity.PolicyNames,
	}

	// Update user using credential manager
	err = s.credentialManager.UpdateUser(ctx, username, updatedIdentity)
	if err != nil {
		return fmt.Errorf("failed to update user policies: %w", err)
	}

	return nil
}

// isNotFoundError checks for "not found" in the error message as a fallback
// for stores (e.g. gRPC) that don't return the credential.ErrAccessKeyNotFound sentinel.
func isNotFoundError(err error) bool {
	return err != nil && strings.Contains(strings.ToLower(err.Error()), "not found")
}

// Helper functions for generating keys and IDs
func generateAccessKey() string {
	// Generate 20-character access key (AWS standard)
	const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	b := make([]byte, 20)
	for i := range b {
		b[i] = charset[randomInt(len(charset))]
	}
	return string(b)
}

func generateSecretKey() string {
	// Use the IAM helper to generate URL-safe secret keys (no +, / characters)
	// that won't break S3 signature authentication
	key, err := iam.GenerateSecretAccessKey()
	if err != nil {
		panic(fmt.Sprintf("failed to generate secret key: %v", err))
	}
	return key
}

func generateAccountId() string {
	// Generate 12-digit account ID
	b := make([]byte, 4)
	rand.Read(b)
	val := (uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]))
	return fmt.Sprintf("%012d", val)
}

func randomInt(max int) int {
	b := make([]byte, 1)
	rand.Read(b)
	return int(b[0]) % max
}
