// Package gcp provides the Google Cloud Platform provider implementation.
// This file contains the permission management methods.
//
// Requirements: 3.2, 3.3, 3.4, 3.6
package gcp

import (
	"context"
	"fmt"
	"strings"

	"google.golang.org/api/cloudresourcemanager/v3"

	"AutoSSO/internal/provider"
)

// AddPermission adds permission for a user in GCP.
// It adds IAM role bindings for each permission set in the request.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - req: Permission request containing project ID, user email, and permission sets
//
// Returns:
//   - error: Any error that occurred during the operation
//
// Requirements: 3.2
func (p *GCPProvider) 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("project ID 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")
	}

	// Normalize the email
	userEmail := strings.TrimSpace(req.UserEmail)

	// Add each permission set
	var lastErr error
	for _, permissionSetName := range req.PermissionSets {
		// Get the GCP IAM roles for this custom role name
		gcpRoles, err := p.getRoleMappings(permissionSetName)
		if err != nil {
			lastErr = fmt.Errorf("failed to get role mappings for %s: %w", permissionSetName, err)
			continue
		}

		// Add each GCP IAM role
		for _, gcpRole := range gcpRoles {
			err = p.addRoleBinding(ctx, req.AccountID, userEmail, gcpRole)
			if err != nil {
				lastErr = fmt.Errorf("failed to add role %s: %w", gcpRole, err)
				continue
			}
		}
	}

	return lastErr
}

// RemovePermission removes permission from a user in GCP.
// It removes IAM role bindings for each permission set in the request.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - req: Permission request containing project ID, user email, and permission sets
//
// Returns:
//   - error: Any error that occurred during the operation
//
// Requirements: 3.3
func (p *GCPProvider) 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("project ID 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")
	}

	// Normalize the email
	userEmail := strings.TrimSpace(req.UserEmail)

	// Remove each permission set
	var lastErr error
	for _, permissionSetName := range req.PermissionSets {
		// Get the GCP IAM roles for this custom role name
		gcpRoles, err := p.getRoleMappings(permissionSetName)
		if err != nil {
			lastErr = fmt.Errorf("failed to get role mappings for %s: %w", permissionSetName, err)
			continue
		}

		// Remove each GCP IAM role
		for _, gcpRole := range gcpRoles {
			err = p.removeRoleBinding(ctx, req.AccountID, userEmail, gcpRole)
			if err != nil {
				lastErr = fmt.Errorf("failed to remove role %s: %w", gcpRole, err)
				continue
			}
		}
	}

	return lastErr
}

// addRoleBinding adds an IAM role binding for a user on a project.
// This grants the user the specified role on the project.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - projectID: The GCP project ID
//   - userEmail: The user's email address
//   - role: The GCP IAM role to grant
//
// Returns:
//   - error: Any error that occurred during the operation
func (p *GCPProvider) addRoleBinding(ctx context.Context, projectID, userEmail, role string) error {
	// Get the current IAM policy
	req := &cloudresourcemanager.GetIamPolicyRequest{}
	policy, err := p.crmService.Projects.GetIamPolicy("projects/"+projectID, req).Context(ctx).Do()
	if err != nil {
		return fmt.Errorf("failed to get IAM policy: %w", err)
	}

	// Format the member string
	member := fmt.Sprintf("user:%s", userEmail)

	// Check if the role binding already exists
	var roleBindingExists bool
	for _, binding := range policy.Bindings {
		if binding.Role == role {
			// Check if the user is already a member
			for _, m := range binding.Members {
				if m == member {
					// User already has this role, nothing to do
					return nil
				}
			}
			// Add the user to the existing binding
			binding.Members = append(binding.Members, member)
			roleBindingExists = true
			break
		}
	}

	// If the role binding doesn't exist, create a new one
	if !roleBindingExists {
		newBinding := &cloudresourcemanager.Binding{
			Role:    role,
			Members: []string{member},
		}
		policy.Bindings = append(policy.Bindings, newBinding)
	}

	// Set the updated IAM policy
	setReq := &cloudresourcemanager.SetIamPolicyRequest{
		Policy: policy,
	}
	_, err = p.crmService.Projects.SetIamPolicy("projects/"+projectID, setReq).Context(ctx).Do()
	if err != nil {
		return fmt.Errorf("failed to set IAM policy: %w", err)
	}

	return nil
}

// removeRoleBinding removes an IAM role binding for a user on a project.
// This revokes the user's specified role on the project.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - projectID: The GCP project ID
//   - userEmail: The user's email address
//   - role: The GCP IAM role to revoke
//
// Returns:
//   - error: Any error that occurred during the operation
func (p *GCPProvider) removeRoleBinding(ctx context.Context, projectID, userEmail, role string) error {
	// Get the current IAM policy
	req := &cloudresourcemanager.GetIamPolicyRequest{}
	policy, err := p.crmService.Projects.GetIamPolicy("projects/"+projectID, req).Context(ctx).Do()
	if err != nil {
		return fmt.Errorf("failed to get IAM policy: %w", err)
	}

	// Format the member string
	member := fmt.Sprintf("user:%s", userEmail)

	// Find and remove the member from the role binding
	var modified bool
	for i, binding := range policy.Bindings {
		if binding.Role == role {
			// Find and remove the member
			for j, m := range binding.Members {
				if m == member {
					// Remove the member using safe slice deletion
					binding.Members = append(binding.Members[:j], binding.Members[j+1:]...)
					modified = true
					break
				}
			}

			// If the binding has no more members, remove the entire binding
			if len(binding.Members) == 0 {
				policy.Bindings = append(policy.Bindings[:i], policy.Bindings[i+1:]...)
			}
			break
		}
	}

	// If no modification was made, the user didn't have the role
	if !modified {
		// This is not an error - the user simply didn't have the role
		return nil
	}

	// Set the updated IAM policy
	setReq := &cloudresourcemanager.SetIamPolicyRequest{
		Policy: policy,
	}
	_, err = p.crmService.Projects.SetIamPolicy("projects/"+projectID, setReq).Context(ctx).Do()
	if err != nil {
		return fmt.Errorf("failed to set IAM policy: %w", err)
	}

	return nil
}

// AddRoleBindingDirect adds an IAM role binding directly without using role mappings.
// This is useful when you want to add a specific GCP IAM role directly.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - projectID: The GCP project ID
//   - userEmail: The user's email address
//   - role: The GCP IAM role to grant
//
// Returns:
//   - error: Any error that occurred during the operation
func (p *GCPProvider) AddRoleBindingDirect(ctx context.Context, projectID, userEmail, role string) error {
	p.closeMu.RLock()
	if p.closed {
		p.closeMu.RUnlock()
		return fmt.Errorf("provider is closed")
	}
	p.closeMu.RUnlock()

	return p.addRoleBinding(ctx, projectID, userEmail, role)
}

// RemoveRoleBindingDirect removes an IAM role binding directly without using role mappings.
// This is useful when you want to remove a specific GCP IAM role directly.
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - projectID: The GCP project ID
//   - userEmail: The user's email address
//   - role: The GCP IAM role to revoke
//
// Returns:
//   - error: Any error that occurred during the operation
func (p *GCPProvider) RemoveRoleBindingDirect(ctx context.Context, projectID, userEmail, role string) error {
	p.closeMu.RLock()
	if p.closed {
		p.closeMu.RUnlock()
		return fmt.Errorf("provider is closed")
	}
	p.closeMu.RUnlock()

	return p.removeRoleBinding(ctx, projectID, userEmail, role)
}
