// Package azure provides the Microsoft Azure provider implementation.
// This file contains the permission management methods.
//
// Requirements: 3.2, 3.3, 3.4, 3.6
package azure

import (
	"context"
	"fmt"
	"strings"

	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2"
	"github.com/google/uuid"

	"AutoSSO/internal/provider"
)

// AddPermission adds permission for a user in Azure.
// It creates role assignments for each permission set in the request.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - req: Permission request containing subscription ID/name, user email, and permission sets
//
// Returns:
//   - error: Any error that occurred during the operation
//
// Requirements: 3.2
func (p *AzureProvider) AddPermission(ctx context.Context, req *provider.PermissionRequest) error {
	p.closeMu.RLock()
	if p.closed {
		p.closeMu.RUnlock()
		return fmt.Errorf("provider is closed")
	}
	p.closeMu.RUnlock()

	if req == nil {
		return fmt.Errorf("permission request cannot be nil")
	}

	if req.AccountID == "" {
		return fmt.Errorf("subscription ID or name is required")
	}

	if req.UserEmail == "" {
		return fmt.Errorf("user email is required")
	}

	if len(req.PermissionSets) == 0 {
		return fmt.Errorf("at least one permission set is required")
	}

	// Resolve subscription identifier to ID and get role suffix
	subscriptionID, roleSuffix, err := p.resolveSubscription(req.AccountID)
	if err != nil {
		return fmt.Errorf("failed to resolve subscription: %w", err)
	}

	// Get the user's Azure AD Object ID
	userObjectID, err := p.GetUserID(ctx, req.UserEmail)
	if err != nil {
		return fmt.Errorf("failed to get user object ID for %s: %w", req.UserEmail, err)
	}

	if userObjectID == "" {
		return fmt.Errorf("user not found: %s", req.UserEmail)
	}

	// Get the role assignments client for this subscription
	roleAssignmentsClient, err := p.getRoleAssignmentsClient(subscriptionID)
	if err != nil {
		return fmt.Errorf("failed to get role assignments client: %w", err)
	}

	// Add each permission set
	var lastErr error
	for _, permissionSetName := range req.PermissionSets {
		// Apply role suffix based on subscription configuration
		roleName := p.applyRoleSuffix(permissionSetName, roleSuffix)

		// Get the role definition ID
		roleDefID, err := p.getRoleDefinitionID(ctx, subscriptionID, roleName)
		if err != nil {
			lastErr = fmt.Errorf("failed to get role definition ID for %s: %w", roleName, err)
			continue
		}

		// Create the role assignment
		err = p.createRoleAssignment(ctx, roleAssignmentsClient, subscriptionID, userObjectID, roleDefID)
		if err != nil {
			lastErr = fmt.Errorf("failed to add permission %s: %w", roleName, err)
			continue
		}
	}

	return lastErr
}

// RemovePermission removes permission from a user in Azure.
// It deletes role assignments for each permission set in the request.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - req: Permission request containing subscription ID/name, user email, and permission sets
//
// Returns:
//   - error: Any error that occurred during the operation
//
// Requirements: 3.3
func (p *AzureProvider) RemovePermission(ctx context.Context, req *provider.PermissionRequest) error {
	p.closeMu.RLock()
	if p.closed {
		p.closeMu.RUnlock()
		return fmt.Errorf("provider is closed")
	}
	p.closeMu.RUnlock()

	if req == nil {
		return fmt.Errorf("permission request cannot be nil")
	}

	if req.AccountID == "" {
		return fmt.Errorf("subscription ID or name is required")
	}

	if req.UserEmail == "" {
		return fmt.Errorf("user email is required")
	}

	if len(req.PermissionSets) == 0 {
		return fmt.Errorf("at least one permission set is required")
	}

	// Resolve subscription identifier to ID and get role suffix
	subscriptionID, roleSuffix, err := p.resolveSubscription(req.AccountID)
	if err != nil {
		return fmt.Errorf("failed to resolve subscription: %w", err)
	}

	// Get the user's Azure AD Object ID
	userObjectID, err := p.GetUserID(ctx, req.UserEmail)
	if err != nil {
		return fmt.Errorf("failed to get user object ID for %s: %w", req.UserEmail, err)
	}

	if userObjectID == "" {
		return fmt.Errorf("user not found: %s", req.UserEmail)
	}

	// Get the role assignments client for this subscription
	roleAssignmentsClient, err := p.getRoleAssignmentsClient(subscriptionID)
	if err != nil {
		return fmt.Errorf("failed to get role assignments client: %w", err)
	}

	// Remove each permission set
	var lastErr error
	for _, permissionSetName := range req.PermissionSets {
		// Apply role suffix based on subscription configuration
		roleName := p.applyRoleSuffix(permissionSetName, roleSuffix)

		// Get the role definition ID
		roleDefID, err := p.getRoleDefinitionID(ctx, subscriptionID, roleName)
		if err != nil {
			lastErr = fmt.Errorf("failed to get role definition ID for %s: %w", roleName, err)
			continue
		}

		// Delete the role assignment
		err = p.deleteRoleAssignment(ctx, roleAssignmentsClient, subscriptionID, userObjectID, roleDefID)
		if err != nil {
			lastErr = fmt.Errorf("failed to remove permission %s: %w", roleName, err)
			continue
		}
	}

	return lastErr
}

// createRoleAssignment creates a role assignment for a user.
// This grants the user the specified role on the subscription.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - client: The role assignments client
//   - subscriptionID: The Azure subscription ID
//   - userObjectID: The Azure AD user Object ID
//   - roleDefinitionID: The role definition ID
//
// Returns:
//   - error: Any error that occurred during the operation
func (p *AzureProvider) createRoleAssignment(
	ctx context.Context,
	client *armauthorization.RoleAssignmentsClient,
	subscriptionID, userObjectID, roleDefinitionID string,
) error {
	scope := fmt.Sprintf("/subscriptions/%s", subscriptionID)
	assignmentName := uuid.New().String()

	_, err := client.Create(ctx, scope, assignmentName, armauthorization.RoleAssignmentCreateParameters{
		Properties: &armauthorization.RoleAssignmentProperties{
			PrincipalID:      &userObjectID,
			RoleDefinitionID: &roleDefinitionID,
		},
	}, nil)

	if err != nil {
		// Check if the error is because the assignment already exists
		if strings.Contains(err.Error(), "RoleAssignmentExists") {
			// Assignment already exists, not an error
			return nil
		}
		return fmt.Errorf("failed to create role assignment: %w", err)
	}

	return nil
}

// deleteRoleAssignment deletes a role assignment for a user.
// This revokes the user's specified role on the subscription.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - client: The role assignments client
//   - subscriptionID: The Azure subscription ID
//   - userObjectID: The Azure AD user Object ID
//   - roleDefinitionID: The role definition ID
//
// Returns:
//   - error: Any error that occurred during the operation
func (p *AzureProvider) deleteRoleAssignment(
	ctx context.Context,
	client *armauthorization.RoleAssignmentsClient,
	subscriptionID, userObjectID, roleDefinitionID string,
) error {
	scope := fmt.Sprintf("/subscriptions/%s", subscriptionID)

	// Find the role assignment to delete
	pager := client.NewListForScopePager(scope, nil)
	for pager.More() {
		page, err := pager.NextPage(ctx)
		if err != nil {
			return fmt.Errorf("failed to list role assignments: %w", err)
		}

		for _, assignment := range page.Value {
			if assignment.Properties != nil &&
				assignment.Properties.PrincipalID != nil &&
				assignment.Properties.RoleDefinitionID != nil &&
				*assignment.Properties.PrincipalID == userObjectID &&
				*assignment.Properties.RoleDefinitionID == roleDefinitionID {
				// Found the matching assignment, delete it
				_, err = client.Delete(ctx, scope, *assignment.Name, nil)
				if err != nil {
					return fmt.Errorf("failed to delete role assignment: %w", err)
				}
				return nil
			}
		}
	}

	// No matching assignment found - this is not an error
	// The user simply doesn't have this role
	return nil
}

// AssignRoleDirect assigns a role directly without using the permission request structure.
// This is useful for direct role assignment operations.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - userEmail: The user's email address
//   - roleName: The role name to assign
//   - subscriptionID: The Azure subscription ID
//
// Returns:
//   - error: Any error that occurred during the operation
func (p *AzureProvider) AssignRoleDirect(ctx context.Context, userEmail, roleName, subscriptionID string) error {
	p.closeMu.RLock()
	if p.closed {
		p.closeMu.RUnlock()
		return fmt.Errorf("provider is closed")
	}
	p.closeMu.RUnlock()

	// Get the user's Azure AD Object ID
	userObjectID, err := p.GetUserID(ctx, userEmail)
	if err != nil {
		return fmt.Errorf("failed to get user object ID: %w", err)
	}

	// Get the role definition ID
	roleDefID, err := p.getRoleDefinitionID(ctx, subscriptionID, roleName)
	if err != nil {
		return fmt.Errorf("failed to get role definition ID: %w", err)
	}

	// Get the role assignments client
	client, err := p.getRoleAssignmentsClient(subscriptionID)
	if err != nil {
		return fmt.Errorf("failed to get role assignments client: %w", err)
	}

	return p.createRoleAssignment(ctx, client, subscriptionID, userObjectID, roleDefID)
}

// RemoveRoleDirect removes a role directly without using the permission request structure.
// This is useful for direct role removal operations.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - userEmail: The user's email address
//   - roleName: The role name to remove
//   - subscriptionID: The Azure subscription ID
//
// Returns:
//   - error: Any error that occurred during the operation
func (p *AzureProvider) RemoveRoleDirect(ctx context.Context, userEmail, roleName, subscriptionID string) error {
	p.closeMu.RLock()
	if p.closed {
		p.closeMu.RUnlock()
		return fmt.Errorf("provider is closed")
	}
	p.closeMu.RUnlock()

	// Get the user's Azure AD Object ID
	userObjectID, err := p.GetUserID(ctx, userEmail)
	if err != nil {
		return fmt.Errorf("failed to get user object ID: %w", err)
	}

	// Get the role definition ID
	roleDefID, err := p.getRoleDefinitionID(ctx, subscriptionID, roleName)
	if err != nil {
		return fmt.Errorf("failed to get role definition ID: %w", err)
	}

	// Get the role assignments client
	client, err := p.getRoleAssignmentsClient(subscriptionID)
	if err != nil {
		return fmt.Errorf("failed to get role assignments client: %w", err)
	}

	return p.deleteRoleAssignment(ctx, client, subscriptionID, userObjectID, roleDefID)
}
