// Package alibaba provides the Alibaba Cloud SSO provider implementation.
// This file contains the permission management methods.
//
// Requirements: 3.2, 3.3, 3.4, 3.6
package alibaba

import (
	"context"
	"fmt"
	"strings"

	cloudsso20210515 "github.com/alibabacloud-go/cloudsso-20210515/client"
	util "github.com/alibabacloud-go/tea-utils/v2/service"
	"github.com/alibabacloud-go/tea/tea"

	"AutoSSO/internal/pkg/logger"
	"AutoSSO/internal/provider"
)

const (
	// TargetTypeRDAccount is the target type for Resource Directory accounts
	TargetTypeRDAccount = "RD-Account"

	// PrincipalTypeUser is the principal type for users
	PrincipalTypeUser = "User"
)

// AddPermission adds permission for a user in Alibaba Cloud SSO.
// It creates an access assignment for each permission set in the request.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - req: Permission request containing account ID, user email, and permission sets
//
// Returns:
//   - error: Any error that occurred during the operation
//
// Requirements: 3.2
func (p *AlibabaProvider) AddPermission(ctx context.Context, req *provider.PermissionRequest) error {
	// Check for nil request first to avoid nil pointer dereference in logging
	if req == nil {
		if p.logger != nil {
			p.logger.Error("[Alibaba] AddPermission failed: request is nil")
		}
		return fmt.Errorf("permission request cannot be nil")
	}

	if p.logger != nil {
		p.logger.Info("[Alibaba] AddPermission called",
			logger.String("account_id", req.AccountID),
			logger.String("user_email", req.UserEmail),
			logger.Any("permission_sets", req.PermissionSets),
		)
	}

	p.closeMu.RLock()
	if p.closed {
		p.closeMu.RUnlock()
		if p.logger != nil {
			p.logger.Error("[Alibaba] AddPermission failed: provider is closed")
		}
		return fmt.Errorf("provider is closed")
	}
	p.closeMu.RUnlock()

	if req.AccountID == "" {
		if p.logger != nil {
			p.logger.Error("[Alibaba] AddPermission failed: account ID is empty")
		}
		return fmt.Errorf("account ID is required")
	}

	if req.UserEmail == "" {
		if p.logger != nil {
			p.logger.Error("[Alibaba] AddPermission failed: user email is empty")
		}
		return fmt.Errorf("user email is required")
	}

	if len(req.PermissionSets) == 0 {
		if p.logger != nil {
			p.logger.Error("[Alibaba] AddPermission failed: permission sets is empty")
		}
		return fmt.Errorf("at least one permission set is required")
	}

	// Get the user ID from email
	if p.logger != nil {
		p.logger.Debug("[Alibaba] Looking up user ID",
			logger.String("email", req.UserEmail),
		)
	}
	userID, err := p.GetUserID(ctx, req.UserEmail)
	if err != nil {
		if p.logger != nil {
			p.logger.Error("[Alibaba] Failed to get user ID",
				logger.String("email", req.UserEmail),
				logger.Err(err),
			)
		}
		return fmt.Errorf("failed to get user ID for %s: %w", req.UserEmail, err)
	}

	if userID == "" {
		if p.logger != nil {
			p.logger.Error("[Alibaba] User not found in Cloud SSO directory",
				logger.String("email", req.UserEmail),
			)
		}
		return fmt.Errorf("user not found: %s", req.UserEmail)
	}

	if p.logger != nil {
		p.logger.Info("[Alibaba] User ID resolved",
			logger.String("email", req.UserEmail),
			logger.String("user_id", userID),
		)
	}

	// Normalize account ID
	accountID := strings.TrimSpace(req.AccountID)

	// Add each permission set
	var lastErr error
	successCount := 0
	for _, permissionSetName := range req.PermissionSets {
		if p.logger != nil {
			p.logger.Info("[Alibaba] Processing permission set",
				logger.String("permission_set", permissionSetName),
				logger.String("account_id", accountID),
			)
		}

		// Get the access configuration ID
		accessConfigID, err := p.getAccessConfigurationID(ctx, permissionSetName)
		if err != nil {
			if p.logger != nil {
				p.logger.Error("[Alibaba] Failed to get access configuration ID",
					logger.String("permission_set", permissionSetName),
					logger.Err(err),
				)
			}
			lastErr = fmt.Errorf("failed to get access configuration ID for %s: %w", permissionSetName, err)
			continue
		}

		if p.logger != nil {
			p.logger.Debug("[Alibaba] Access configuration ID resolved",
				logger.String("permission_set", permissionSetName),
				logger.String("access_config_id", accessConfigID),
			)
		}

		// Create the access assignment
		err = p.createAccessAssignment(ctx, accountID, userID, accessConfigID)
		if err != nil {
			// Check if the error is due to assignment already existing (idempotency)
			errMsg := err.Error()
			if strings.Contains(errMsg, "EntityAlreadyExists") || 
			   strings.Contains(errMsg, "No modification is made") {
				if p.logger != nil {
					p.logger.Info("[Alibaba] Access assignment already exists (idempotent)",
						logger.String("permission_set", permissionSetName),
						logger.String("account_id", accountID),
						logger.String("user_id", userID),
					)
				}
				// Treat as success
				successCount++
				continue
			}

			if p.logger != nil {
				p.logger.Error("[Alibaba] Failed to create access assignment",
					logger.String("permission_set", permissionSetName),
					logger.String("account_id", accountID),
					logger.String("user_id", userID),
					logger.Err(err),
				)
			}
			lastErr = fmt.Errorf("failed to add permission %s: %w", permissionSetName, err)
			continue
		}

		successCount++
		if p.logger != nil {
			p.logger.Info("[Alibaba] Successfully added permission",
				logger.String("permission_set", permissionSetName),
				logger.String("account_id", accountID),
				logger.String("user_email", req.UserEmail),
			)
		}
	}

	if p.logger != nil {
		p.logger.Info("[Alibaba] AddPermission completed",
			logger.Int("success_count", successCount),
			logger.Int("total_count", len(req.PermissionSets)),
			logger.Bool("has_errors", lastErr != nil),
		)
	}

	return lastErr
}

// RemovePermission removes permission from a user in Alibaba Cloud SSO.
// It deletes the access assignment for each permission set in the request.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - req: Permission request containing account ID, user email, and permission sets
//
// Returns:
//   - error: Any error that occurred during the operation
//
// Requirements: 3.3
func (p *AlibabaProvider) RemovePermission(ctx context.Context, req *provider.PermissionRequest) error {
	// Check for nil request first to avoid nil pointer dereference in logging
	if req == nil {
		if p.logger != nil {
			p.logger.Error("[Alibaba] RemovePermission failed: request is nil")
		}
		return fmt.Errorf("permission request cannot be nil")
	}

	if p.logger != nil {
		p.logger.Info("[Alibaba] RemovePermission called",
			logger.String("account_id", req.AccountID),
			logger.String("user_email", req.UserEmail),
			logger.Any("permission_sets", req.PermissionSets),
		)
	}

	p.closeMu.RLock()
	if p.closed {
		p.closeMu.RUnlock()
		if p.logger != nil {
			p.logger.Error("[Alibaba] RemovePermission failed: provider is closed")
		}
		return fmt.Errorf("provider is closed")
	}
	p.closeMu.RUnlock()

	if req.AccountID == "" {
		if p.logger != nil {
			p.logger.Error("[Alibaba] RemovePermission failed: account ID is empty")
		}
		return fmt.Errorf("account ID is required")
	}

	if req.UserEmail == "" {
		if p.logger != nil {
			p.logger.Error("[Alibaba] RemovePermission failed: user email is empty")
		}
		return fmt.Errorf("user email is required")
	}

	if len(req.PermissionSets) == 0 {
		if p.logger != nil {
			p.logger.Error("[Alibaba] RemovePermission failed: permission sets is empty")
		}
		return fmt.Errorf("at least one permission set is required")
	}

	// Get the user ID from email
	if p.logger != nil {
		p.logger.Debug("[Alibaba] Looking up user ID for removal",
			logger.String("email", req.UserEmail),
		)
	}
	userID, err := p.GetUserID(ctx, req.UserEmail)
	if err != nil {
		if p.logger != nil {
			p.logger.Error("[Alibaba] Failed to get user ID for removal",
				logger.String("email", req.UserEmail),
				logger.Err(err),
			)
		}
		return fmt.Errorf("failed to get user ID for %s: %w", req.UserEmail, err)
	}

	if userID == "" {
		if p.logger != nil {
			p.logger.Error("[Alibaba] User not found for removal",
				logger.String("email", req.UserEmail),
			)
		}
		return fmt.Errorf("user not found: %s", req.UserEmail)
	}

	if p.logger != nil {
		p.logger.Info("[Alibaba] User ID resolved for removal",
			logger.String("email", req.UserEmail),
			logger.String("user_id", userID),
		)
	}

	// Normalize account ID
	accountID := strings.TrimSpace(req.AccountID)

	// Remove each permission set
	var lastErr error
	successCount := 0
	for _, permissionSetName := range req.PermissionSets {
		if p.logger != nil {
			p.logger.Info("[Alibaba] Processing permission set removal",
				logger.String("permission_set", permissionSetName),
				logger.String("account_id", accountID),
			)
		}

		// Get the access configuration ID
		accessConfigID, err := p.getAccessConfigurationID(ctx, permissionSetName)
		if err != nil {
			if p.logger != nil {
				p.logger.Error("[Alibaba] Failed to get access configuration ID for removal",
					logger.String("permission_set", permissionSetName),
					logger.Err(err),
				)
			}
			lastErr = fmt.Errorf("failed to get access configuration ID for %s: %w", permissionSetName, err)
			continue
		}

		// Delete the access assignment
		err = p.deleteAccessAssignment(ctx, accountID, userID, accessConfigID)
		if err != nil {
			if p.logger != nil {
				p.logger.Error("[Alibaba] Failed to delete access assignment",
					logger.String("permission_set", permissionSetName),
					logger.String("account_id", accountID),
					logger.String("user_id", userID),
					logger.Err(err),
				)
			}
			lastErr = fmt.Errorf("failed to remove permission %s: %w", permissionSetName, err)
			continue
		}

		successCount++
		if p.logger != nil {
			p.logger.Info("[Alibaba] Successfully removed permission",
				logger.String("permission_set", permissionSetName),
				logger.String("account_id", accountID),
				logger.String("user_email", req.UserEmail),
			)
		}
	}

	if p.logger != nil {
		p.logger.Info("[Alibaba] RemovePermission completed",
			logger.Int("success_count", successCount),
			logger.Int("total_count", len(req.PermissionSets)),
			logger.Bool("has_errors", lastErr != nil),
		)
	}

	return lastErr
}

// createAccessAssignment creates an access assignment for a user.
// This grants the user access to the specified account with the specified access configuration.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - accountID: The Alibaba Cloud account ID (target)
//   - userID: The Cloud SSO user ID (principal)
//   - accessConfigID: The access configuration ID
//
// Returns:
//   - error: Any error that occurred during the operation
func (p *AlibabaProvider) createAccessAssignment(ctx context.Context, accountID, userID, accessConfigID string) error {
	req := &cloudsso20210515.CreateAccessAssignmentRequest{
		DirectoryId:           tea.String(p.config.DirectoryID),
		AccessConfigurationId: tea.String(accessConfigID),
		PrincipalId:           tea.String(userID),
		PrincipalType:         tea.String(PrincipalTypeUser),
		TargetId:              tea.String(accountID),
		TargetType:            tea.String(TargetTypeRDAccount),
	}

	if p.logger != nil {
		p.logger.Info("Calling CreateAccessAssignment",
			logger.String("directory_id", *req.DirectoryId),
			logger.String("access_configuration_id", *req.AccessConfigurationId),
			logger.String("principal_id", *req.PrincipalId),
			logger.String("principal_type", *req.PrincipalType),
			logger.String("target_id", *req.TargetId),
			logger.String("target_type", *req.TargetType),
		)
	}

	_, err := p.client.CreateAccessAssignmentWithOptions(req, &util.RuntimeOptions{})
	if err != nil {
		return fmt.Errorf("failed to create access assignment: %w", err)
	}

	return nil
}

// deleteAccessAssignment deletes an access assignment for a user.
// This revokes the user's access to the specified account with the specified access configuration.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - accountID: The Alibaba Cloud account ID (target)
//   - userID: The Cloud SSO user ID (principal)
//   - accessConfigID: The access configuration ID
//
// Returns:
//   - error: Any error that occurred during the operation
func (p *AlibabaProvider) deleteAccessAssignment(ctx context.Context, accountID, userID, accessConfigID string) error {
	req := &cloudsso20210515.DeleteAccessAssignmentRequest{
		DirectoryId:           tea.String(p.config.DirectoryID),
		PrincipalId:           tea.String(userID),
		PrincipalType:         tea.String(PrincipalTypeUser),
		TargetId:              tea.String(accountID),
		TargetType:            tea.String(TargetTypeRDAccount),
		AccessConfigurationId: tea.String(accessConfigID),
	}

	runtime := &util.RuntimeOptions{}

	if p.logger != nil {
		p.logger.Info("Calling DeleteAccessAssignment",
			logger.String("directory_id", *req.DirectoryId),
			logger.String("access_configuration_id", *req.AccessConfigurationId),
			logger.String("principal_id", *req.PrincipalId),
			logger.String("principal_type", *req.PrincipalType),
			logger.String("target_id", *req.TargetId),
			logger.String("target_type", *req.TargetType),
		)
	}

	// Use a deferred recover to handle any panics from the SDK
	var err error
	func() {
		defer func() {
			if r := tea.Recover(recover()); r != nil {
				err = r
			}
		}()
		_, err = p.client.DeleteAccessAssignmentWithOptions(req, runtime)
	}()

	if err != nil {
		return fmt.Errorf("failed to delete access assignment: %w", err)
	}

	return nil
}

// AssignPermissionDirect assigns a permission directly without using the permission request structure.
// This is useful for direct permission assignment operations.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - accountID: The Alibaba Cloud account ID
//   - userEmail: The user's email address
//   - permissionName: The permission/access configuration name
//
// Returns:
//   - error: Any error that occurred during the operation
func (p *AlibabaProvider) AssignPermissionDirect(ctx context.Context, accountID, userEmail, permissionName string) error {
	p.closeMu.RLock()
	if p.closed {
		p.closeMu.RUnlock()
		return fmt.Errorf("provider is closed")
	}
	p.closeMu.RUnlock()

	if accountID == "" || userEmail == "" || permissionName == "" {
		return fmt.Errorf("accountID, userEmail, and permissionName are all required")
	}

	// Get the user ID
	userID, err := p.GetUserID(ctx, userEmail)
	if err != nil {
		return fmt.Errorf("failed to get user ID: %w", err)
	}

	// Get the access configuration ID
	accessConfigID, err := p.getAccessConfigurationID(ctx, permissionName)
	if err != nil {
		return fmt.Errorf("failed to get access configuration ID: %w", err)
	}

	// Normalize account ID
	accountID = strings.TrimSpace(accountID)

	return p.createAccessAssignment(ctx, accountID, userID, accessConfigID)
}

// RemovePermissionDirect removes a permission directly without using the permission request structure.
// This is useful for direct permission removal operations.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - accountID: The Alibaba Cloud account ID
//   - userEmail: The user's email address
//   - permissionName: The permission/access configuration name
//
// Returns:
//   - error: Any error that occurred during the operation
func (p *AlibabaProvider) RemovePermissionDirect(ctx context.Context, accountID, userEmail, permissionName string) error {
	p.closeMu.RLock()
	if p.closed {
		p.closeMu.RUnlock()
		return fmt.Errorf("provider is closed")
	}
	p.closeMu.RUnlock()

	if accountID == "" || userEmail == "" || permissionName == "" {
		return fmt.Errorf("accountID, userEmail, and permissionName are all required")
	}

	// Get the user ID
	userID, err := p.GetUserID(ctx, userEmail)
	if err != nil {
		return fmt.Errorf("failed to get user ID: %w", err)
	}

	// Get the access configuration ID
	accessConfigID, err := p.getAccessConfigurationID(ctx, permissionName)
	if err != nil {
		return fmt.Errorf("failed to get access configuration ID: %w", err)
	}

	// Normalize account ID
	accountID = strings.TrimSpace(accountID)

	return p.deleteAccessAssignment(ctx, accountID, userID, accessConfigID)
}
