// Package provider defines the unified cloud provider interface and registry.
// It provides an abstraction layer for managing permissions across multiple
// cloud platforms (AWS, GCP, Azure, Alibaba Cloud).
//
// Requirements: 3.1, 3.5
package provider

import (
	"context"
	"fmt"
	"sync"

	"AutoSSO/internal/config"
)

// Provider is the unified interface for cloud platform providers.
// All cloud platform implementations (AWS, GCP, Azure, Alibaba) must
// implement this interface to enable consistent permission management.
//
// Requirements: 3.1
type Provider interface {
	// Name returns the provider name (e.g., "AWS", "GCP", "Azure", "Alibaba")
	Name() string

	// AddPermission adds permission for a user in the cloud platform.
	// It assigns the specified permission sets to the user for the given account.
	//
	// Parameters:
	//   - ctx: Context for cancellation and timeout
	//   - req: Permission request containing account, user, and permission details
	//
	// Returns:
	//   - error: Any error that occurred during the operation
	//
	// Requirements: 3.2
	AddPermission(ctx context.Context, req *PermissionRequest) error

	// RemovePermission removes permission from a user in the cloud platform.
	// It revokes the specified permission sets from the user for the given account.
	//
	// Parameters:
	//   - ctx: Context for cancellation and timeout
	//   - req: Permission request containing account, user, and permission details
	//
	// Returns:
	//   - error: Any error that occurred during the operation
	//
	// Requirements: 3.3
	RemovePermission(ctx context.Context, req *PermissionRequest) error

	// GetUserID retrieves the cloud platform user ID for a given email address.
	// This is used to map Feishu user emails to cloud platform user identities.
	//
	// Parameters:
	//   - ctx: Context for cancellation and timeout
	//   - email: User's email address
	//
	// Returns:
	//   - string: The cloud platform user ID
	//   - error: Any error that occurred during the lookup
	GetUserID(ctx context.Context, email string) (string, error)

	// ValidateCredentials validates that the provider's credentials are valid
	// and the provider can successfully authenticate with the cloud platform.
	//
	// Parameters:
	//   - ctx: Context for cancellation and timeout
	//
	// Returns:
	//   - error: Any error if credentials are invalid or authentication fails
	ValidateCredentials(ctx context.Context) error

	// Close releases any resources held by the provider.
	// This should be called when the provider is no longer needed.
	//
	// Returns:
	//   - error: Any error that occurred during cleanup
	Close() error
}

// PermissionRequest represents a request to add or remove permissions.
// It contains all the information needed to perform a permission operation.
type PermissionRequest struct {
	// AccountID is the cloud account/project/subscription ID
	// For AWS: Account ID (e.g., "557537366020")
	// For GCP: Project ID (e.g., "tidbcloud-prod-000")
	// For Azure: Subscription ID
	// For Alibaba: Account ID
	AccountID string

	// UserEmail is the email address of the user
	UserEmail string

	// PermissionSets is the list of permission set names to add or remove
	// These are the actual cloud platform permission/role names
	PermissionSets []string
}

// PermissionResult represents the result of a permission operation.
// It indicates whether the operation was successful for a specific permission set.
type PermissionResult struct {
	// Success indicates whether the operation was successful
	Success bool

	// PermissionSet is the name of the permission set that was processed
	PermissionSet string

	// Error contains any error that occurred during the operation
	// This is nil if Success is true
	Error error
}

// ProviderType represents the type of cloud platform provider.
// It is used to identify and categorize providers in the registry.
//
// Requirements: 3.5
type ProviderType string

const (
	// ProviderAWS represents Amazon Web Services
	ProviderAWS ProviderType = "AWS"

	// ProviderGCP represents Google Cloud Platform
	ProviderGCP ProviderType = "GCP"

	// ProviderAzure represents Microsoft Azure
	ProviderAzure ProviderType = "Azure"

	// ProviderAlibaba represents Alibaba Cloud
	ProviderAlibaba ProviderType = "Alibaba"
)

// String returns the string representation of the ProviderType.
func (pt ProviderType) String() string {
	return string(pt)
}

// IsValid checks if the ProviderType is a valid known type.
func (pt ProviderType) IsValid() bool {
	switch pt {
	case ProviderAWS, ProviderGCP, ProviderAzure, ProviderAlibaba:
		return true
	default:
		return false
	}
}

// Registry is the provider registry that manages cloud platform providers.
// It provides a centralized way to access providers by account ID or type,
// eliminating the need for hardcoded switch-case statements.
//
// Requirements: 3.5
type Registry struct {
	// mu protects concurrent access to the registry maps
	mu sync.RWMutex

	// providers maps provider types to their implementations
	providers map[ProviderType]Provider

	// accounts maps account IDs to their provider types
	// This enables looking up the correct provider for any account
	accounts map[string]ProviderType
}

// NewRegistry creates a new provider registry initialized from the configuration.
// It builds the account-to-provider mapping from the configured cloud accounts.
//
// Parameters:
//   - cfg: The application configuration containing cloud account definitions
//
// Returns:
//   - *Registry: The initialized registry
//   - error: Any error that occurred during initialization
//
// Requirements: 3.5, 9.5
func NewRegistry(cfg *config.Config) (*Registry, error) {
	if cfg == nil {
		return nil, fmt.Errorf("config cannot be nil")
	}

	r := &Registry{
		providers: make(map[ProviderType]Provider),
		accounts:  make(map[string]ProviderType),
	}

	// Build account-to-provider mapping from AWS accounts
	for _, account := range cfg.Providers.AWS.Accounts {
		if account.ID != "" {
			r.accounts[account.ID] = ProviderAWS
		}
	}

	// Build account-to-provider mapping from GCP projects
	for _, project := range cfg.Providers.GCP.Projects {
		if project.ID != "" {
			r.accounts[project.ID] = ProviderGCP
		}
	}

	// Build account-to-provider mapping from Azure subscriptions
	for _, subscription := range cfg.Providers.Azure.Subscriptions {
		if subscription.ID != "" {
			r.accounts[subscription.ID] = ProviderAzure
		}
		// Also map by name for Azure (commonly used)
		if subscription.Name != "" {
			r.accounts[subscription.Name] = ProviderAzure
		}
	}

	// Build account-to-provider mapping from Alibaba accounts
	for _, account := range cfg.Providers.Alibaba.Accounts {
		if account.ID != "" {
			r.accounts[account.ID] = ProviderAlibaba
		}
	}

	return r, nil
}

// RegisterProvider registers a provider implementation for a given type.
// This should be called during application initialization to register
// all cloud platform provider implementations.
//
// Parameters:
//   - t: The provider type to register
//   - p: The provider implementation
//
// Returns:
//   - error: An error if the provider type is invalid or already registered
func (r *Registry) RegisterProvider(t ProviderType, p Provider) error {
	if !t.IsValid() {
		return fmt.Errorf("invalid provider type: %s", t)
	}
	if p == nil {
		return fmt.Errorf("provider cannot be nil")
	}

	r.mu.Lock()
	defer r.mu.Unlock()

	if _, exists := r.providers[t]; exists {
		return fmt.Errorf("provider already registered for type: %s", t)
	}

	r.providers[t] = p
	return nil
}

// GetProvider returns the provider for a given account ID.
// It looks up the provider type from the account mapping and returns
// the corresponding provider implementation.
//
// Parameters:
//   - accountID: The cloud account/project/subscription ID
//
// Returns:
//   - Provider: The provider implementation for the account
//   - error: An error if the account is not found or provider not registered
//
// Requirements: 3.5
func (r *Registry) GetProvider(accountID string) (Provider, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	providerType, exists := r.accounts[accountID]
	if !exists {
		return nil, fmt.Errorf("no provider found for account: %s", accountID)
	}

	provider, exists := r.providers[providerType]
	if !exists {
		return nil, fmt.Errorf("provider not registered for type: %s", providerType)
	}

	return provider, nil
}

// GetProviderByType returns the provider for a given provider type.
//
// Parameters:
//   - t: The provider type
//
// Returns:
//   - Provider: The provider implementation
//   - error: An error if the provider type is not registered
func (r *Registry) GetProviderByType(t ProviderType) (Provider, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	provider, exists := r.providers[t]
	if !exists {
		return nil, fmt.Errorf("provider not registered for type: %s", t)
	}

	return provider, nil
}

// GetProviderType returns the provider type for a given account ID.
// This is useful when you need to know the cloud platform type
// without getting the full provider implementation.
//
// Parameters:
//   - accountID: The cloud account/project/subscription ID
//
// Returns:
//   - ProviderType: The provider type for the account
//   - bool: True if the account was found, false otherwise
func (r *Registry) GetProviderType(accountID string) (ProviderType, bool) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	providerType, exists := r.accounts[accountID]
	return providerType, exists
}

// ListAccounts returns all registered account IDs for a given provider type.
//
// Parameters:
//   - t: The provider type to filter by
//
// Returns:
//   - []string: List of account IDs for the provider type
func (r *Registry) ListAccounts(t ProviderType) []string {
	r.mu.RLock()
	defer r.mu.RUnlock()

	var accounts []string
	for accountID, providerType := range r.accounts {
		if providerType == t {
			accounts = append(accounts, accountID)
		}
	}
	return accounts
}

// ListProviderTypes returns all registered provider types.
//
// Returns:
//   - []ProviderType: List of registered provider types
func (r *Registry) ListProviderTypes() []ProviderType {
	r.mu.RLock()
	defer r.mu.RUnlock()

	types := make([]ProviderType, 0, len(r.providers))
	for t := range r.providers {
		types = append(types, t)
	}
	return types
}

// Close closes all registered providers and releases their resources.
// This should be called during application shutdown.
//
// Returns:
//   - error: The first error encountered during closing, if any
func (r *Registry) Close() error {
	r.mu.Lock()
	defer r.mu.Unlock()

	var firstErr error
	for t, p := range r.providers {
		if err := p.Close(); err != nil {
			if firstErr == nil {
				firstErr = fmt.Errorf("failed to close provider %s: %w", t, err)
			}
		}
	}

	// Clear the maps
	r.providers = make(map[ProviderType]Provider)
	r.accounts = make(map[string]ProviderType)

	return firstErr
}

// AccountCount returns the total number of registered accounts.
func (r *Registry) AccountCount() int {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return len(r.accounts)
}

// ProviderCount returns the total number of registered providers.
func (r *Registry) ProviderCount() int {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return len(r.providers)
}
