// Package permission provides the core permission management logic for AutoSSO.
// It handles adding, removing, and scheduling removal of cloud platform permissions
// by integrating with cloud providers, state storage, and Feishu notifications.
//
// Requirements: 3.2, 3.3, 10.1
package permission

import (
	"context"
	"fmt"
	"strings"
	"time"

	"AutoSSO/internal/config"
	"AutoSSO/internal/feishu"
	"AutoSSO/internal/pkg/logger"
	"AutoSSO/internal/provider"
	"AutoSSO/internal/state"
)

// Manager is the central permission management component that coordinates
// permission operations across cloud platforms. It integrates with cloud
// providers, state storage, and Feishu for notifications.
//
// Requirements: 3.2, 3.3, 10.1
type Manager struct {
	providers    *provider.Registry
	feishu       *feishu.Client
	notification *feishu.NotificationService
	state        state.Store
	config       *config.Config
	logger       *logger.Logger
}

// NewManager creates a new permission manager with all required dependencies.
//
// Parameters:
//   - providers: Registry of cloud platform providers
//   - feishuClient: Feishu API client for notifications
//   - stateStore: State storage for tracking permissions
//   - cfg: Application configuration
//   - log: Logger instance
//
// Returns a new Manager instance.
func NewManager(
	providers *provider.Registry,
	feishuClient *feishu.Client,
	stateStore state.Store,
	cfg *config.Config,
	log *logger.Logger,
) *Manager {
	var notification *feishu.NotificationService
	if feishuClient != nil {
		notification = feishu.NewNotificationService(feishuClient, &cfg.Feishu)
	}

	return &Manager{
		providers:    providers,
		feishu:       feishuClient,
		notification: notification,
		state:        stateStore,
		config:       cfg,
		logger:       log.WithModule("permission-manager"),
	}
}

// AddPermissions adds permissions for a user based on the approval data.
// This method:
// 1. Gets the appropriate cloud platform provider from the registry
// 2. Checks if permission is in pending removal list and removes it if so
// 3. Calls provider.AddPermission for each permission set
// 4. Sends notification via Feishu
// 5. Logs the operation
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - data: The approval data containing user email, account ID, and permission sets
//
// Returns an error if any step fails.
//
// Requirements: 3.2, 10.1
func (m *Manager) AddPermissions(ctx context.Context, data *feishu.ApprovalData) error {
	if data == nil {
		return fmt.Errorf("approval data cannot be nil")
	}

	if !data.IsValidApprovalData() {
		return fmt.Errorf("invalid approval data: missing required fields")
	}

	m.logger.Info("************ Starting permission addition ************",
		logger.UserEmail(data.Email),
		logger.AccountID(data.AccountID),
		logger.CloudType(data.CloudType),
		logger.Any("permission_sets", data.PermissionSets),
	)

	// 1. Get the appropriate cloud platform provider
	prov, err := m.providers.GetProvider(data.AccountID)
	if err != nil {
		m.logger.Error("Failed to get provider for account",
			logger.AccountID(data.AccountID),
			logger.Err(err),
		)
		return fmt.Errorf("failed to get provider for account %s: %w", data.AccountID, err)
	}

	// Track successful and failed permission additions
	var addedPermissions []string
	var failedPermissions []string

	// Process each permission set
	for _, permissionSet := range data.PermissionSets {
		permissionSet = strings.TrimSpace(permissionSet)
		if permissionSet == "" {
			continue
		}

		// 2. Check if permission is in pending removal list and remove it if so
		if err := m.removePendingRemovalIfExists(ctx, data.AccountID, data.Email, permissionSet); err != nil {
			m.logger.Warn("Failed to check/remove pending removal",
				logger.UserEmail(data.Email),
				logger.AccountID(data.AccountID),
				logger.String("permission_set", permissionSet),
				logger.Err(err),
			)
			// Continue with adding permission even if this fails
		}

		// 3. Call provider.AddPermission
		req := &provider.PermissionRequest{
			AccountID:      data.AccountID,
			UserEmail:      data.Email,
			PermissionSets: []string{permissionSet},
		}

		if err := prov.AddPermission(ctx, req); err != nil {
			m.logger.Error("Failed to add permission",
				logger.UserEmail(data.Email),
				logger.AccountID(data.AccountID),
				logger.String("permission_set", permissionSet),
				logger.Err(err),
			)
			failedPermissions = append(failedPermissions, permissionSet)
			continue
		}

		addedPermissions = append(addedPermissions, permissionSet)

		m.logger.Info("Permission added successfully",
			logger.UserEmail(data.Email),
			logger.AccountID(data.AccountID),
			logger.String("permission_set", permissionSet),
		)
	}

	// 4. Send notification via Feishu (only if at least one permission was added)
	if len(addedPermissions) > 0 && m.notification != nil {
		// Create a copy of approval data with only the successfully added permissions
		notifyData := &feishu.ApprovalData{
			Email:          data.Email,
			CloudType:      data.CloudType,
			AccountID:      data.AccountID,
			PermissionSets: addedPermissions,
		}

		if err := m.notification.NotifyPermissionAdded(ctx, notifyData); err != nil {
			m.logger.Warn("Failed to send permission added notification",
				logger.UserEmail(data.Email),
				logger.Err(err),
			)
			// Don't return error for notification failure
		} else {
			m.logger.Info("Permission added notification sent",
				logger.UserEmail(data.Email),
			)
		}
	}

	// Return error if all permissions failed
	if len(failedPermissions) > 0 && len(addedPermissions) == 0 {
		return fmt.Errorf("failed to add all permissions: %v. Check logs for details", failedPermissions)
	}

	// Return partial error if some permissions failed
	if len(failedPermissions) > 0 {
		return fmt.Errorf("failed to add some permissions: %v (succeeded: %v). Check logs for details", failedPermissions, addedPermissions)
	}

	return nil
}

// RemovePermissions removes permissions for a user based on the pending removal record.
// This method:
// 1. Gets the appropriate cloud platform provider
// 2. Calls provider.RemovePermission
// 3. Sends notification via Feishu
// 4. Removes from pending removal list
// 5. Logs the operation
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - removal: The pending removal record containing account, user, and permission details
//
// Returns an error if any step fails.
//
// Requirements: 3.3, 10.3
func (m *Manager) RemovePermissions(ctx context.Context, removal *state.PendingRemoval) error {
	if removal == nil {
		return fmt.Errorf("pending removal cannot be nil")
	}

	m.logger.Info("Starting permission removal",
		logger.UserEmail(removal.Email),
		logger.AccountID(removal.AccountID),
		logger.CloudType(removal.CloudType),
		logger.String("permission_set", removal.PermissionSet),
	)

	// 1. Get the appropriate cloud platform provider
	prov, err := m.providers.GetProvider(removal.AccountID)
	if err != nil {
		m.logger.Error("Failed to get provider for account",
			logger.AccountID(removal.AccountID),
			logger.Err(err),
		)
		return fmt.Errorf("failed to get provider for account %s: %w", removal.AccountID, err)
	}

	// 2. Call provider.RemovePermission
	req := &provider.PermissionRequest{
		AccountID:      removal.AccountID,
		UserEmail:      removal.Email,
		PermissionSets: []string{removal.PermissionSet},
	}

	if err := prov.RemovePermission(ctx, req); err != nil {
		m.logger.Error("Failed to remove permission",
			logger.UserEmail(removal.Email),
			logger.AccountID(removal.AccountID),
			logger.String("permission_set", removal.PermissionSet),
			logger.Err(err),
		)
		return fmt.Errorf("failed to remove permission %s: %w", removal.PermissionSet, err)
	}

	m.logger.Info("Permission removed successfully",
		logger.UserEmail(removal.Email),
		logger.AccountID(removal.AccountID),
		logger.String("permission_set", removal.PermissionSet),
	)

	// 3. Send notification via Feishu
	if m.notification != nil {
		notifyData := &feishu.ApprovalData{
			Email:          removal.Email,
			CloudType:      removal.CloudType,
			AccountID:      removal.AccountID,
			PermissionSets: []string{removal.PermissionSet},
		}

		if err := m.notification.NotifyPermissionRemoved(ctx, notifyData); err != nil {
			m.logger.Warn("Failed to send permission removed notification",
				logger.UserEmail(removal.Email),
				logger.Err(err),
			)
			// Don't return error for notification failure
		} else {
			m.logger.Info("Permission removed notification sent",
				logger.UserEmail(removal.Email),
			)
		}
	}

	// 4. Remove from pending removal list
	if m.state != nil && removal.ID > 0 {
		if err := m.state.DeletePendingRemoval(ctx, removal.ID); err != nil {
			m.logger.Warn("Failed to delete pending removal record",
				logger.Int64("removal_id", removal.ID),
				logger.Err(err),
			)
			// Don't return error for state cleanup failure
		} else {
			m.logger.Info("Pending removal record deleted",
				logger.Int64("removal_id", removal.ID),
			)
		}
	}

	return nil
}

// ScheduleRemoval schedules permission removal after the grace period.
// This method:
// 1. Checks if the user is in the whitelist (skip if so)
// 2. Creates pending removal record in state store
// 3. Sends expiring notification
//
// Parameters:
//   - ctx: Context for cancellation and timeout
//   - data: The approval data containing user email, account ID, and permission sets
//
// Returns an error if any step fails.
//
// Requirements: 10.2, 10.3
func (m *Manager) ScheduleRemoval(ctx context.Context, data *feishu.ApprovalData) error {
	if data == nil {
		return fmt.Errorf("approval data cannot be nil")
	}

	m.logger.Info("Scheduling permission removal",
		logger.UserEmail(data.Email),
		logger.AccountID(data.AccountID),
		logger.CloudType(data.CloudType),
		logger.Any("permission_sets", data.PermissionSets),
	)

	// 1. Check if user is in whitelist
	if m.isWhitelisted(data.Email) {
		m.logger.Info("User is whitelisted, skipping removal scheduling",
			logger.UserEmail(data.Email),
		)
		return nil
	}

	// Calculate scheduled removal time (expired time + grace period)
	gracePeriod := time.Duration(m.config.Runtime.GracePeriodDays) * 24 * time.Hour
	scheduledAt := data.EndTime.Add(gracePeriod)

	// Track scheduled permissions
	var scheduledPermissions []string

	// Process each permission set
	for _, permissionSet := range data.PermissionSets {
		permissionSet = strings.TrimSpace(permissionSet)
		if permissionSet == "" {
			continue
		}

		// 2. Create pending removal record
		removal := state.NewPendingRemoval(
			data.AccountID,
			data.Email,
			permissionSet,
			data.CloudType,
			data.EndTime,
			scheduledAt,
		)

		if m.state != nil {
			if err := m.state.SavePendingRemoval(ctx, removal); err != nil {
				m.logger.Error("Failed to save pending removal",
					logger.UserEmail(data.Email),
					logger.AccountID(data.AccountID),
					logger.String("permission_set", permissionSet),
					logger.Err(err),
				)
				continue
			}

			m.logger.Info("Pending removal scheduled",
				logger.UserEmail(data.Email),
				logger.AccountID(data.AccountID),
				logger.String("permission_set", permissionSet),
				logger.Time("scheduled_at", scheduledAt),
			)
		}

		scheduledPermissions = append(scheduledPermissions, permissionSet)
	}

	// 3. Send expiring notification (only if at least one permission was scheduled)
	if len(scheduledPermissions) > 0 && m.notification != nil {
		notifyData := &feishu.ApprovalData{
			Email:          data.Email,
			CloudType:      data.CloudType,
			AccountID:      data.AccountID,
			PermissionSets: scheduledPermissions,
		}

		daysRemaining := m.config.Runtime.GracePeriodDays
		if err := m.notification.NotifyPermissionExpiring(ctx, notifyData, daysRemaining); err != nil {
			m.logger.Warn("Failed to send permission expiring notification",
				logger.UserEmail(data.Email),
				logger.Err(err),
			)
			// Don't return error for notification failure
		} else {
			m.logger.Info("Permission expiring notification sent",
				logger.UserEmail(data.Email),
				logger.Int("days_remaining", daysRemaining),
			)
		}
	}

	if len(scheduledPermissions) == 0 {
		return fmt.Errorf("failed to schedule any permission removals")
	}

	return nil
}

// removePendingRemovalIfExists checks if a permission is in the pending removal list
// and removes it if found. This is called before adding a permission to ensure
// that re-approved permissions are not accidentally removed.
func (m *Manager) removePendingRemovalIfExists(ctx context.Context, accountID, email, permissionSet string) error {
	if m.state == nil {
		return nil
	}

	// Get all pending removals
	removals, err := m.state.GetPendingRemovals(ctx)
	if err != nil {
		return fmt.Errorf("failed to get pending removals: %w", err)
	}

	// Find and delete matching pending removal
	for _, removal := range removals {
		if removal.AccountID == accountID &&
			removal.Email == email &&
			removal.PermissionSet == permissionSet {

			if err := m.state.DeletePendingRemoval(ctx, removal.ID); err != nil {
				return fmt.Errorf("failed to delete pending removal: %w", err)
			}

			m.logger.Info("Removed pending removal for re-approved permission",
				logger.UserEmail(email),
				logger.AccountID(accountID),
				logger.String("permission_set", permissionSet),
			)
			break
		}
	}

	return nil
}

// isWhitelisted checks if the user email is in the whitelist.
// Whitelisted users will not have their permissions automatically revoked.
func (m *Manager) isWhitelisted(email string) bool {
	if m.config == nil {
		return false
	}

	email = strings.ToLower(strings.TrimSpace(email))
	for _, whitelistedEmail := range m.config.Runtime.WhitelistEmails {
		if strings.ToLower(strings.TrimSpace(whitelistedEmail)) == email {
			return true
		}
	}

	return false
}

// GetPendingRemovals returns all pending permission removals that are ready to be processed.
// A removal is ready if the current time is after the scheduled removal time.
func (m *Manager) GetPendingRemovals(ctx context.Context) ([]*state.PendingRemoval, error) {
	if m.state == nil {
		return nil, fmt.Errorf("state store not configured")
	}

	removals, err := m.state.GetPendingRemovals(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to get pending removals: %w", err)
	}

	// Filter to only return removals that are ready
	var readyRemovals []*state.PendingRemoval
	for _, removal := range removals {
		if removal.IsReadyForRemoval() {
			readyRemovals = append(readyRemovals, removal)
		}
	}

	return readyRemovals, nil
}

// ProcessPendingRemovals processes all pending removals that are ready.
// It iterates through all ready removals and calls RemovePermissions for each.
//
// Returns the number of successfully processed removals and any errors encountered.
func (m *Manager) ProcessPendingRemovals(ctx context.Context) (int, []error) {
	readyRemovals, err := m.GetPendingRemovals(ctx)
	if err != nil {
		return 0, []error{err}
	}

	var processed int
	var errors []error

	for _, removal := range readyRemovals {
		if err := m.RemovePermissions(ctx, removal); err != nil {
			errors = append(errors, fmt.Errorf("failed to remove permission for %s: %w", removal.Email, err))
			continue
		}
		processed++
	}

	return processed, errors
}

// ConvertFeishuPermission converts a Feishu permission name to the actual cloud platform
// permission name using the configured permission mappings.
func (m *Manager) ConvertFeishuPermission(feishuName string) string {
	if m.config == nil {
		return feishuName
	}

	feishuName = strings.TrimSpace(feishuName)
	feishuNameLower := strings.ToLower(feishuName)

	for _, mapping := range m.config.PermissionMappings {
		if strings.ToLower(mapping.FeishuName) == feishuNameLower {
			return mapping.RealName
		}
	}

	// If no mapping found, return the original name
	return feishuName
}

// GetProviderType returns the provider type for a given account ID.
// This is useful for determining which cloud platform an account belongs to.
func (m *Manager) GetProviderType(accountID string) (provider.ProviderType, bool) {
	if m.providers == nil {
		return "", false
	}
	return m.providers.GetProviderType(accountID)
}
