// Package permission provides the core permission management logic for AutoSSO.
// This file contains the Lifecycle component that manages the complete lifecycle
// of permissions from creation to expiration and removal.
//
// Requirements: 10.3, 10.6
package permission

import (
	"context"
	"fmt"
	"sync"
	"time"

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

// Lifecycle manages the complete lifecycle of permissions.
// It handles permission expiration detection, grace period management,
// scheduled removal execution, and daily statistics reporting.
//
// Requirements: 10.3, 10.6
type Lifecycle struct {
	manager      *Manager
	notification *feishu.NotificationService
	state        state.Store
	config       *config.Config
	logger       *logger.Logger

	// stats holds the daily statistics for permission operations
	stats   *feishu.DailyStats
	statsMu sync.RWMutex

	// stopCh is used to signal the daily report scheduler to stop
	stopCh chan struct{}
	// stopOnce ensures Stop is idempotent
	stopOnce sync.Once
}

// NewLifecycle creates a new lifecycle manager.
//
// Parameters:
//   - manager: Permission manager for adding/removing permissions
//   - notification: Notification service for sending daily reports
//   - stateStore: State store for tracking permissions
//   - cfg: Application configuration
//   - log: Logger instance
//
// Returns a new Lifecycle instance.
func NewLifecycle(
	manager *Manager,
	notification *feishu.NotificationService,
	stateStore state.Store,
	cfg *config.Config,
	log *logger.Logger,
) *Lifecycle {
	return &Lifecycle{
		manager:      manager,
		notification: notification,
		state:        stateStore,
		config:       cfg,
		logger:       log.WithModule("permission-lifecycle"),
		stats:        feishu.NewDailyStats(),
		stopCh:       make(chan struct{}),
	}
}

// StartDailyReportScheduler starts the daily report scheduler.
// It sends daily statistics reports at a configured time (default: 18:00).
// The scheduler continues until the context is cancelled.
//
// Parameters:
//   - ctx: Context for cancellation and graceful shutdown
//
// Returns an error if the scheduler fails to start.
//
// Requirements: 10.5
func (l *Lifecycle) StartDailyReportScheduler(ctx context.Context) error {
	if l.notification == nil {
		return fmt.Errorf("notification service is not configured")
	}

	l.logger.Info("Starting daily report scheduler")

	// Calculate time until next report (default: 18:00)
	reportHour := 18
	reportMinute := 0

	for {
		// Calculate duration until next report time
		now := time.Now()
		nextReport := time.Date(now.Year(), now.Month(), now.Day(), reportHour, reportMinute, 0, 0, now.Location())

		// If we've passed today's report time, schedule for tomorrow
		if now.After(nextReport) {
			nextReport = nextReport.Add(24 * time.Hour)
		}

		waitDuration := time.Until(nextReport)

		l.logger.Info("Next daily report scheduled",
			logger.Time("next_report_time", nextReport),
			logger.Duration("wait_duration", waitDuration),
		)

		select {
		case <-ctx.Done():
			l.logger.Info("Daily report scheduler stopped")
			return ctx.Err()
		case <-l.stopCh:
			l.logger.Info("Daily report scheduler stopped by signal")
			return nil
		case <-time.After(waitDuration):
			// Send the daily report
			if err := l.SendDailyReport(ctx); err != nil {
				l.logger.Error("Failed to send daily report",
					logger.Err(err),
				)
			}
		}
	}
}

// Stop stops the lifecycle manager and its schedulers.
func (l *Lifecycle) Stop() {
	l.stopOnce.Do(func() {
		close(l.stopCh)
	})
}

// SendDailyReport sends the daily statistics report.
// It collects the current statistics and sends them via the notification service,
// then resets the statistics for the next day.
//
// Parameters:
//   - ctx: Context for cancellation
//
// Returns an error if sending the report fails.
//
// Requirements: 10.5
func (l *Lifecycle) SendDailyReport(ctx context.Context) error {
	if l.notification == nil {
		return fmt.Errorf("notification service is not configured")
	}

	l.statsMu.RLock()
	stats := l.stats
	l.statsMu.RUnlock()

	if stats == nil {
		return fmt.Errorf("statistics not initialized")
	}

	// Update report time
	stats.ReportTime = time.Now()

	l.logger.Info("Sending daily report",
		logger.Time("report_time", stats.ReportTime),
		logger.Int("aws_added", stats.AWS.TotalAdded),
		logger.Int("aws_removed", stats.AWS.TotalRemoved),
		logger.Int("gcp_added", stats.GCP.TotalAdded),
		logger.Int("gcp_removed", stats.GCP.TotalRemoved),
		logger.Int("azure_added", stats.Azure.TotalAdded),
		logger.Int("azure_removed", stats.Azure.TotalRemoved),
		logger.Int("alibaba_added", stats.Alibaba.TotalAdded),
		logger.Int("alibaba_removed", stats.Alibaba.TotalRemoved),
	)

	// Send the report
	if err := l.notification.SendDailyReport(ctx, stats); err != nil {
		return fmt.Errorf("failed to send daily report: %w", err)
	}

	l.logger.Info("Daily report sent successfully")

	// Reset statistics for the next day
	l.ResetStats()

	return nil
}

// RecordPermissionAdded records a permission addition for statistics.
// This should be called whenever a permission is successfully added.
//
// Parameters:
//   - cloudType: The cloud platform type (AWS, GCP, Azure, Alibaba)
//   - permissionSet: The name of the permission set that was added
func (l *Lifecycle) RecordPermissionAdded(cloudType, permissionSet string) {
	l.statsMu.Lock()
	defer l.statsMu.Unlock()

	if l.stats == nil {
		l.stats = feishu.NewDailyStats()
	}

	l.stats.RecordPermissionAdded(cloudType, permissionSet)

	l.logger.Debug("Recorded permission addition",
		logger.CloudType(cloudType),
		logger.String("permission_set", permissionSet),
	)
}

// RecordPermissionRemoved records a permission removal for statistics.
// This should be called whenever a permission is successfully removed.
//
// Parameters:
//   - cloudType: The cloud platform type (AWS, GCP, Azure, Alibaba)
//   - permissionSet: The name of the permission set that was removed
//   - isNonGeneral: Whether this is a non-general permission set (AWS specific)
func (l *Lifecycle) RecordPermissionRemoved(cloudType, permissionSet string, isNonGeneral bool) {
	l.statsMu.Lock()
	defer l.statsMu.Unlock()

	if l.stats == nil {
		l.stats = feishu.NewDailyStats()
	}

	l.stats.RecordPermissionRemoved(cloudType, permissionSet, isNonGeneral)

	l.logger.Debug("Recorded permission removal",
		logger.CloudType(cloudType),
		logger.String("permission_set", permissionSet),
		logger.Bool("is_non_general", isNonGeneral),
	)
}

// ResetStats resets the daily statistics.
// This is typically called after sending the daily report.
func (l *Lifecycle) ResetStats() {
	l.statsMu.Lock()
	defer l.statsMu.Unlock()

	l.stats = feishu.NewDailyStats()

	l.logger.Debug("Statistics reset")
}

// GetStats returns a copy of the current statistics.
// This is useful for testing or monitoring purposes.
func (l *Lifecycle) GetStats() *feishu.DailyStats {
	l.statsMu.RLock()
	defer l.statsMu.RUnlock()

	if l.stats == nil {
		return feishu.NewDailyStats()
	}

	// Return a copy to prevent external modification
	statsCopy := &feishu.DailyStats{
		ReportTime: l.stats.ReportTime,
		AWS: feishu.CloudStats{
			TotalAdded:          l.stats.AWS.TotalAdded,
			TotalRemoved:        l.stats.AWS.TotalRemoved,
			AddedByPermission:   copyMap(l.stats.AWS.AddedByPermission),
			RemovedByPermission: copyMap(l.stats.AWS.RemovedByPermission),
			NonGeneralRemoved:   l.stats.AWS.NonGeneralRemoved,
		},
		GCP: feishu.CloudStats{
			TotalAdded:          l.stats.GCP.TotalAdded,
			TotalRemoved:        l.stats.GCP.TotalRemoved,
			AddedByPermission:   copyMap(l.stats.GCP.AddedByPermission),
			RemovedByPermission: copyMap(l.stats.GCP.RemovedByPermission),
		},
		Azure: feishu.CloudStats{
			TotalAdded:          l.stats.Azure.TotalAdded,
			TotalRemoved:        l.stats.Azure.TotalRemoved,
			AddedByPermission:   copyMap(l.stats.Azure.AddedByPermission),
			RemovedByPermission: copyMap(l.stats.Azure.RemovedByPermission),
		},
		Alibaba: feishu.CloudStats{
			TotalAdded:          l.stats.Alibaba.TotalAdded,
			TotalRemoved:        l.stats.Alibaba.TotalRemoved,
			AddedByPermission:   copyMap(l.stats.Alibaba.AddedByPermission),
			RemovedByPermission: copyMap(l.stats.Alibaba.RemovedByPermission),
		},
	}

	return statsCopy
}

// copyMap creates a copy of a map[string]int.
func copyMap(m map[string]int) map[string]int {
	if m == nil {
		return make(map[string]int)
	}
	result := make(map[string]int, len(m))
	for k, v := range m {
		result[k] = v
	}
	return result
}

// ProcessExpiredPermissions checks for expired permissions and schedules their removal.
// This method:
// 1. Gets all approved instances from the state store
// 2. Checks if any have expired
// 3. Schedules removal for expired permissions (after grace period)
// 4. Updates instance status to expired
//
// Parameters:
//   - ctx: Context for cancellation
//
// Returns an error if processing fails.
//
// Requirements: 10.2, 10.3
func (l *Lifecycle) ProcessExpiredPermissions(ctx context.Context) error {
	if l.state == nil {
		return fmt.Errorf("state store is not configured")
	}

	l.logger.Debug("Checking for expired permissions")

	// Get all approved instances
	filter := &state.InstanceFilter{
		Status: state.StatusApproved,
	}

	instances, err := l.state.ListInstances(ctx, filter)
	if err != nil {
		return fmt.Errorf("failed to list instances: %w", err)
	}

	var processedCount int
	var errorCount int

	for _, instance := range instances {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
		}

		// Check if instance has expired
		if !instance.IsExpired() {
			continue
		}

		l.logger.Info("Found expired permission",
			logger.InstanceID(instance.ID),
			logger.UserEmail(instance.Email),
			logger.CloudType(instance.CloudType),
			logger.AccountID(instance.AccountID),
		)

		// Convert to ApprovalData for scheduling
		approvalData := &feishu.ApprovalData{
			Email:          instance.Email,
			CloudType:      instance.CloudType,
			AccountID:      instance.AccountID,
			PermissionSets: instance.PermissionSets,
			StartTime:      instance.StartTime,
			EndTime:        instance.EndTime,
			IsApproved:     true,
			IsExpired:      true,
		}

		// Schedule removal using the manager
		if l.manager != nil {
			if err := l.manager.ScheduleRemoval(ctx, approvalData); err != nil {
				l.logger.Error("Failed to schedule removal",
					logger.InstanceID(instance.ID),
					logger.Err(err),
				)
				errorCount++
				continue
			}
		}

		// Update instance status to expired
		instance.Status = state.StatusExpired
		if err := l.state.SaveInstance(ctx, instance); err != nil {
			l.logger.Error("Failed to update instance status",
				logger.InstanceID(instance.ID),
				logger.Err(err),
			)
			errorCount++
			continue
		}

		processedCount++
	}

	if processedCount > 0 || errorCount > 0 {
		l.logger.Info("Expired permissions processing completed",
			logger.Int("processed", processedCount),
			logger.Int("errors", errorCount),
		)
	}

	return nil
}

// ProcessScheduledRemovals processes all scheduled permission removals that are ready.
// This method:
// 1. Gets all pending removals from the state store
// 2. Filters to only those that are ready (past scheduled time)
// 3. Removes permissions via the manager
// 4. Records statistics for each removal
//
// Parameters:
//   - ctx: Context for cancellation
//
// Returns the number of successfully processed removals and any errors encountered.
//
// Requirements: 10.3
func (l *Lifecycle) ProcessScheduledRemovals(ctx context.Context) (int, []error) {
	if l.manager == nil {
		return 0, []error{fmt.Errorf("permission manager is not configured")}
	}

	l.logger.Debug("Processing scheduled removals")

	// Get pending removals that are ready
	readyRemovals, err := l.manager.GetPendingRemovals(ctx)
	if err != nil {
		return 0, []error{fmt.Errorf("failed to get pending removals: %w", err)}
	}

	if len(readyRemovals) == 0 {
		l.logger.Debug("No scheduled removals ready for processing")
		return 0, nil
	}

	l.logger.Info("Found scheduled removals ready for processing",
		logger.Int("count", len(readyRemovals)),
	)

	var processed int
	var errors []error

	for _, removal := range readyRemovals {
		select {
		case <-ctx.Done():
			return processed, append(errors, ctx.Err())
		default:
		}

		l.logger.Info("Processing scheduled removal",
			logger.UserEmail(removal.Email),
			logger.AccountID(removal.AccountID),
			logger.CloudType(removal.CloudType),
			logger.String("permission_set", removal.PermissionSet),
		)

		// Remove the permission
		if err := l.manager.RemovePermissions(ctx, removal); err != nil {
			l.logger.Error("Failed to remove permission",
				logger.UserEmail(removal.Email),
				logger.AccountID(removal.AccountID),
				logger.String("permission_set", removal.PermissionSet),
				logger.Err(err),
			)
			errors = append(errors, fmt.Errorf("failed to remove permission for %s: %w", removal.Email, err))
			continue
		}

		// Record the removal in statistics
		// Determine if this is a non-general permission set (AWS specific)
		isNonGeneral := l.isNonGeneralPermissionSet(removal.CloudType, removal.PermissionSet)
		l.RecordPermissionRemoved(removal.CloudType, removal.PermissionSet, isNonGeneral)

		processed++

		l.logger.Info("Scheduled removal completed",
			logger.UserEmail(removal.Email),
			logger.AccountID(removal.AccountID),
			logger.String("permission_set", removal.PermissionSet),
		)
	}

	l.logger.Info("Scheduled removals processing completed",
		logger.Int("processed", processed),
		logger.Int("errors", len(errors)),
	)

	return processed, errors
}

// isNonGeneralPermissionSet checks if a permission set is a non-general permission set.
// Non-general permission sets are AWS-specific and are tracked separately in statistics.
func (l *Lifecycle) isNonGeneralPermissionSet(cloudType, permissionSet string) bool {
	// Only AWS has non-general permission sets
	if cloudType != "AWS" {
		return false
	}

	// Use configured general permission sets if available
	if l.config != nil && len(l.config.Runtime.GeneralPermissionSets) > 0 {
		for _, general := range l.config.Runtime.GeneralPermissionSets {
			if general == permissionSet {
				return false
			}
		}
		return true
	}

	// Fallback to default general AWS permission sets if config is empty
	// This ensures backward compatibility and basic functionality
	defaultGeneralPermissionSets := map[string]bool{
		"DBaaS-Prod-ViewOnly-Role":          true,
		"DBaaS-Prod-OnCall-Role":            true,
		"DBaaS-Prod-Billing-Role":           true,
		"DBaaS-Prod-Admin-Role":             true,
		"PingCAP-SecurityAdmin-Role":        true,
		"DBaaS-Prod-EC2-Role":               true,
		"DBaaS-DevUser-Role":                true,
		"DBaaS-Prod-Web-Role":               true,
		"DBaaS-Prod-OrgOperator-Role":       true,
		"Prod-Tiflow-Engine-RDS":            true,
		"DBaaS-DevLeader-Role":              true,
		"DBaaS-Infra-DevUser-Role":          true,
		"DbaaS-Prod-GlueFullAccess":         true,
		"DBaaS-Prod-OnCall-BastionEC2-Role": true,
		"DBaaS-Prod-Support-Role":           true,
	}

	return !defaultGeneralPermissionSets[permissionSet]
}

// StartRemovalScheduler starts the scheduled removal processing loop.
// It checks for and processes scheduled removals at the configured interval.
// The loop continues until the context is cancelled.
//
// Parameters:
//   - ctx: Context for cancellation and graceful shutdown
//
// Returns an error if the scheduler fails to start.
//
// Requirements: 10.3
func (l *Lifecycle) StartRemovalScheduler(ctx context.Context) error {
	if l.manager == nil {
		return fmt.Errorf("permission manager is not configured")
	}

	checkInterval := l.config.Runtime.ExpireCheckInterval
	if checkInterval <= 0 {
		checkInterval = 1 * time.Hour
	}

	l.logger.Info("Starting removal scheduler",
		logger.Duration("check_interval", checkInterval),
	)

	ticker := time.NewTicker(checkInterval)
	defer ticker.Stop()

	if err := l.ProcessExpiredPermissions(ctx); err != nil {
		l.logger.Error("Initial expired permissions processing failed",
			logger.Err(err),
		)
	}

	if _, errs := l.ProcessScheduledRemovals(ctx); len(errs) > 0 {
		for _, err := range errs {
			l.logger.Error("Initial removal processing error",
				logger.Err(err),
			)
		}
	}

	for {
		select {
		case <-ctx.Done():
			l.logger.Info("Removal scheduler stopped")
			return ctx.Err()
		case <-l.stopCh:
			l.logger.Info("Removal scheduler stopped by signal")
			return nil
		case <-ticker.C:
			// Process expired permissions first
			if err := l.ProcessExpiredPermissions(ctx); err != nil {
				l.logger.Error("Expired permissions processing failed",
					logger.Err(err),
				)
			}

			// Then process scheduled removals
			if _, errs := l.ProcessScheduledRemovals(ctx); len(errs) > 0 {
				for _, err := range errs {
					l.logger.Error("Removal processing error",
						logger.Err(err),
					)
				}
			}
		}
	}
}

// GetGracePeriodDays returns the configured grace period in days.
func (l *Lifecycle) GetGracePeriodDays() int {
	if l.config == nil {
		return 3 // Default grace period
	}
	return l.config.Runtime.GracePeriodDays
}

// CalculateScheduledRemovalTime calculates when a permission should be removed
// based on its expiration time and the configured grace period.
func (l *Lifecycle) CalculateScheduledRemovalTime(expiredAt time.Time) time.Time {
	gracePeriod := time.Duration(l.GetGracePeriodDays()) * 24 * time.Hour
	return expiredAt.Add(gracePeriod)
}

// IsPermissionReadyForRemoval checks if a permission is ready to be removed
// based on its expiration time and the configured grace period.
func (l *Lifecycle) IsPermissionReadyForRemoval(expiredAt time.Time) bool {
	scheduledTime := l.CalculateScheduledRemovalTime(expiredAt)
	return time.Now().After(scheduledTime)
}
