// Package permission provides the core permission management logic for AutoSSO.
// This file contains the Monitor component that handles monitoring for new
// approval instances and expired permissions.
//
// Requirements: 10.1, 10.2
package permission

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

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

// Monitor handles monitoring for new approvals and expired permissions.
// It polls Feishu for new approval instances and monitors for permissions
// that need to be removed after expiration.
//
// Requirements: 10.1, 10.2
type Monitor struct {
	manager *Manager
	feishu  *feishu.Client
	parser  *feishu.Parser
	state   state.Store
	config  *config.Config
	logger  *logger.Logger

	// pendingInstances holds instance IDs that are waiting for approval
	pendingInstances map[string]bool
	pendingMu        sync.RWMutex

	// lastPollTime tracks the last time we polled for new instances
	lastPollTime time.Time

	// startupTime tracks when the monitor was created (for skipping historical approvals)
	startupTime time.Time
}

// NewMonitor creates a new permission monitor.
//
// Parameters:
//   - manager: Permission manager for adding/removing permissions
//   - feishuClient: Feishu client for API calls
//   - stateStore: State store for tracking processed instances
//   - cfg: Application configuration
//   - log: Logger instance
//
// Returns a new Monitor instance.
func NewMonitor(
	manager *Manager,
	feishuClient *feishu.Client,
	stateStore state.Store,
	cfg *config.Config,
	log *logger.Logger,
) *Monitor {
	parser := feishu.NewParser().WithLogger(log)
	if cfg != nil {
		parser = parser.WithFieldMappings(cfg.Feishu.FieldMappings)
	}

	now := time.Now()

	// Determine lastPollTime based on config
	// If skip_historical_approvals is enabled, start from now (ignore all historical)
	// Otherwise, start from 24 hours ago to catch recent approvals
	lastPollTime := now.Add(-24 * time.Hour)
	if cfg != nil && cfg.Runtime.SkipHistoricalApprovals {
		lastPollTime = now
	}

	m := &Monitor{
		manager:          manager,
		feishu:           feishuClient,
		parser:           parser,
		state:            stateStore,
		config:           cfg,
		logger:           log.WithModule("permission-monitor"),
		pendingInstances: make(map[string]bool),
		lastPollTime:     lastPollTime,
		startupTime:      now,
	}

	if cfg != nil && cfg.Runtime.SkipHistoricalApprovals {
		m.logger.Info("Skip historical approvals mode enabled - only processing approvals created after startup",
			logger.Time("startup_time", now),
		)
	}

	return m
}

// StartApprovalMonitor starts the approval monitoring loop.
// It polls Feishu for new approval instances 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 monitoring loop fails to start.
//
// Requirements: 10.1
func (m *Monitor) StartApprovalMonitor(ctx context.Context) error {
	if m.feishu == nil {
		return fmt.Errorf("feishu client is not configured")
	}

	pollInterval := m.config.Runtime.PollInterval
	if pollInterval <= 0 {
		pollInterval = 5 * time.Second
	}

	m.logger.Info("Starting approval monitor",
		logger.Duration("poll_interval", pollInterval),
		logger.String("approval_code", m.config.Feishu.ApprovalCode),
	)

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

	// Process immediately on start
	if err := m.ProcessNewApprovals(ctx); err != nil {
		m.logger.Error("Initial approval processing failed",
			logger.Err(err),
		)
	}

	for {
		select {
		case <-ctx.Done():
			m.logger.Info("Approval monitor stopped")
			return ctx.Err()
		case <-ticker.C:
			if err := m.ProcessNewApprovals(ctx); err != nil {
				m.logger.Error("Approval processing failed",
					logger.Err(err),
				)
			}

			// Also process any pending instances
			if err := m.ProcessPendingApprovals(ctx); err != nil {
				m.logger.Error("Pending approval processing failed",
					logger.Err(err),
				)
			}
		}
	}
}

// StartExpirationMonitor starts the expiration monitoring loop.
// It checks for expired permissions 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 monitoring loop fails to start.
//
// Requirements: 10.2
func (m *Monitor) StartExpirationMonitor(ctx context.Context) error {
	if m.state == nil {
		return fmt.Errorf("state store is not configured")
	}

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

	m.logger.Info("Starting expiration monitor",
		logger.Duration("check_interval", checkInterval),
	)

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

	// Process immediately on start
	if err := m.ProcessExpiredPermissions(ctx); err != nil {
		m.logger.Error("Initial expiration processing failed",
			logger.Err(err),
		)
	}

	for {
		select {
		case <-ctx.Done():
			m.logger.Info("Expiration monitor stopped")
			return ctx.Err()
		case <-ticker.C:
			if err := m.ProcessExpiredPermissions(ctx); err != nil {
				m.logger.Error("Expiration processing failed",
					logger.Err(err),
				)
			}
		}
	}
}

// ProcessNewApprovals processes new approval instances.
// It fetches new instances from Feishu, checks their status,
// and adds permissions for approved instances.
//
// Parameters:
//   - ctx: Context for cancellation
//
// Returns an error if processing fails.
//
// Requirements: 10.1
func (m *Monitor) ProcessNewApprovals(ctx context.Context) error {
	if m.feishu == nil {
		return fmt.Errorf("feishu client is not configured")
	}

	// Calculate time range for fetching instances
	endTime := time.Now()
	startTime := m.lastPollTime

	m.logger.Debug("Fetching new approval instances",
		logger.Time("start_time", startTime),
		logger.Time("end_time", endTime),
	)

	// Get new instance IDs from Feishu
	instanceIDs, err := m.feishu.GetNewInstances(ctx, startTime, endTime)
	if err != nil {
		return fmt.Errorf("failed to get new instances: %w", err)
	}

	// Update last poll time
	m.lastPollTime = endTime

	if len(instanceIDs) == 0 {
		m.logger.Info("No new instances found in this poll cycle")
		return nil
	}

	m.logger.Info("Found new instances",
		logger.Int("count", len(instanceIDs)),
	)

	// Process each instance
	var processedCount int
	var errorCount int

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

		if err := m.processInstance(ctx, instanceID); err != nil {
			m.logger.Error("Failed to process instance",
				logger.InstanceID(instanceID),
				logger.Err(err),
			)
			errorCount++
			continue
		}
		processedCount++
	}

	m.logger.Info("Approval processing completed",
		logger.Int("processed", processedCount),
		logger.Int("errors", errorCount),
	)

	return nil
}

// ProcessPendingApprovals checks the status of all pending instances.
// It iterates through the pending list and re-processes each instance.
//
// Parameters:
//   - ctx: Context for cancellation
//
// Returns an error if any processing step fails (logic errors are logged but don't stop the loop).
func (m *Monitor) ProcessPendingApprovals(ctx context.Context) error {
	pendingIDs := m.GetPendingInstances()
	if len(pendingIDs) == 0 {
		return nil
	}

	m.logger.Info("Processing pending approvals",
		logger.Int("count", len(pendingIDs)),
	)

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

		// Re-process the instance
		// If it's approved, processInstance will return nil and remove it from pending list (inside processInstance logic)
		// If it's still pending, it will stay in the list
		// If it's rejected/invalid, it might be removed or stay depending on logic.
		// Note: processInstance calls GetInstanceDetail which is what we need.
		if err := m.processInstance(ctx, instanceID); err != nil {
			m.logger.Error("Failed to re-process pending instance",
				logger.InstanceID(instanceID),
				logger.Err(err),
			)
		}
	}

	return nil
}

// processInstance processes a single approval instance.
// It checks if the instance has been processed, fetches its details,
// and adds permissions if approved.
func (m *Monitor) processInstance(ctx context.Context, instanceID string) error {
	// Check if instance has already been processed
	if m.state != nil {
		deleted, err := m.state.IsInstanceDeleted(ctx, instanceID)
		if err != nil {
			m.logger.Error("Failed to check if instance is deleted",
				logger.InstanceID(instanceID),
				logger.Err(err),
			)
		} else if deleted {
			m.logger.Info("[SKIP] Instance already processed, skipping",
				logger.InstanceID(instanceID),
			)
			return nil
		}
	}

	// Check if feishu client is available
	if m.feishu == nil {
		return fmt.Errorf("feishu client is not configured")
	}

	// Check if instance is in pending list
	m.pendingMu.RLock()
	isPending := m.pendingInstances[instanceID]
	m.pendingMu.RUnlock()

	// Get instance details from Feishu
	rawJSON, err := m.feishu.GetInstanceDetail(ctx, instanceID)
	if err != nil {
		return fmt.Errorf("failed to get instance detail: %w", err)
	}

	// Parse the instance data
	approvalData, err := m.parser.ParseInstanceDetail(rawJSON)
	if err != nil {
		return fmt.Errorf("failed to parse instance detail: %w", err)
	}

	m.logger.Info("[PARSED] Approval data extracted",
		logger.InstanceID(instanceID),
		logger.UserEmail(approvalData.Email),
		logger.CloudType(approvalData.CloudType),
		logger.AccountID(approvalData.AccountID),
		logger.Any("permission_sets", approvalData.PermissionSets),
		logger.Bool("is_approved", approvalData.IsApproved),
		logger.Bool("is_valid", approvalData.IsValidApprovalData()),
		logger.Bool("app_type_is_sso", approvalData.AppType),
	)

	// Check if this is a valid SSO application
	if !approvalData.IsValidApprovalData() {
		m.logger.Warn("[SKIP] Instance is not a valid SSO application",
			logger.InstanceID(instanceID),
			logger.String("reason", "IsValidApprovalData() returned false"),
			logger.String("email", approvalData.Email),
			logger.Bool("app_type_is_sso", approvalData.AppType),
			logger.String("account_id", approvalData.AccountID),
			logger.Any("permission_sets", approvalData.PermissionSets),
		)
		// Mark as processed to avoid re-checking
		if m.state != nil {
			if err := m.state.SaveDeletedInstance(ctx, instanceID); err != nil {
				m.logger.Error("Failed to save deleted instance",
					logger.InstanceID(instanceID),
					logger.Err(err),
				)
			}
		}
		return nil
	}

	// If not approved yet, add to pending list
	if !approvalData.IsApproved {
		if !isPending {
			m.pendingMu.Lock()
			m.pendingInstances[instanceID] = true
			m.pendingMu.Unlock()
			m.logger.Info("[PENDING] Instance pending approval, added to watch list",
				logger.InstanceID(instanceID),
			)
		}
		return nil
	}

	// Remove from pending list if it was there
	if isPending {
		m.pendingMu.Lock()
		delete(m.pendingInstances, instanceID)
		m.pendingMu.Unlock()
	}

	// Check if permission is expired
	if approvalData.IsExpired {
		m.logger.Debug("Instance permission already expired, scheduling removal",
			logger.InstanceID(instanceID),
		)
		// Schedule removal for expired permission
		if m.manager != nil {
			if err := m.manager.ScheduleRemoval(ctx, approvalData); err != nil {
				m.logger.Error("Failed to schedule removal for expired permission",
					logger.InstanceID(instanceID),
					logger.Err(err),
				)
			}
		}
		// Mark as processed
		if m.state != nil {
			if err := m.state.SaveDeletedInstance(ctx, instanceID); err != nil {
				m.logger.Error("Failed to save deleted instance",
					logger.InstanceID(instanceID),
					logger.Err(err),
				)
			}
		}
		return nil
	}

	// Add permissions for approved instance
	m.logger.Info("Processing approved instance",
		logger.InstanceID(instanceID),
		logger.UserEmail(approvalData.Email),
		logger.CloudType(approvalData.CloudType),
		logger.AccountID(approvalData.AccountID),
	)

	if m.manager != nil {
		if err := m.manager.AddPermissions(ctx, approvalData); err != nil {
			return fmt.Errorf("failed to add permissions: %w", err)
		}
	}

	// Save the instance state for expiration monitoring
	if m.state != nil {
		instance := &state.ApprovalInstance{
			ID:             instanceID,
			Email:          approvalData.Email,
			AccountID:      approvalData.AccountID,
			PermissionSets: approvalData.PermissionSets,
			StartTime:      approvalData.StartTime,
			EndTime:        approvalData.EndTime,
			Status:         state.StatusApproved,
			CloudType:      approvalData.CloudType,
			Description:    approvalData.Description,
			CreatedAt:      time.Now(),
			UpdatedAt:      time.Now(),
		}

		if err := m.state.SaveInstance(ctx, instance); err != nil {
			m.logger.Error("Failed to save approval instance",
				logger.InstanceID(instanceID),
				logger.Err(err),
			)
			// Continue to mark as processed even if save fails, to avoid infinite loop
		}
	}

	// Mark instance as processed
	if m.state != nil {
		if err := m.state.SaveDeletedInstance(ctx, instanceID); err != nil {
			m.logger.Error("Failed to mark instance as processed",
				logger.InstanceID(instanceID),
				logger.Err(err),
			)
		}
	}

	return nil
}

// ProcessExpiredPermissions processes expired permissions.
// It checks for permissions that have passed the grace period
// and removes them from the cloud platforms.
//
// Parameters:
//   - ctx: Context for cancellation
//
// Returns an error if processing fails.
//
// Requirements: 10.2, 10.3
func (m *Monitor) ProcessExpiredPermissions(ctx context.Context) error {
	if m.manager == nil {
		return fmt.Errorf("permission manager is not configured")
	}

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

	// Also check for newly expired permissions that need to be scheduled
	if err := m.checkNewlyExpiredPermissions(ctx); err != nil {
		m.logger.Error("Failed to check newly expired permissions",
			logger.Err(err),
		)
	}

	return nil
}

// checkNewlyExpiredPermissions checks for permissions that have just expired
// and schedules them for removal after the grace period.
func (m *Monitor) checkNewlyExpiredPermissions(ctx context.Context) error {
	if m.state == nil {
		return nil
	}

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

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

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

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

		m.logger.Info("Found expired permission, scheduling removal",
			logger.InstanceID(instance.ID),
			logger.UserEmail(instance.Email),
			logger.CloudType(instance.CloudType),
		)

		// 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
		if m.manager != nil {
			if err := m.manager.ScheduleRemoval(ctx, approvalData); err != nil {
				m.logger.Error("Failed to schedule removal",
					logger.InstanceID(instance.ID),
					logger.Err(err),
				)
				continue
			}
		}

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

	return nil
}

// GetPendingInstanceCount returns the number of instances waiting for approval.
func (m *Monitor) GetPendingInstanceCount() int {
	m.pendingMu.RLock()
	defer m.pendingMu.RUnlock()
	return len(m.pendingInstances)
}

// GetPendingInstances returns a copy of the pending instance IDs.
func (m *Monitor) GetPendingInstances() []string {
	m.pendingMu.RLock()
	defer m.pendingMu.RUnlock()

	instances := make([]string, 0, len(m.pendingInstances))
	for id := range m.pendingInstances {
		instances = append(instances, id)
	}
	return instances
}

// ClearPendingInstances clears the pending instances map.
// This is useful for testing or resetting the monitor state.
func (m *Monitor) ClearPendingInstances() {
	m.pendingMu.Lock()
	defer m.pendingMu.Unlock()
	m.pendingInstances = make(map[string]bool)
}

// SetLastPollTime sets the last poll time.
// This is useful for testing or adjusting the polling window.
func (m *Monitor) SetLastPollTime(t time.Time) {
	m.lastPollTime = t
}

// GetLastPollTime returns the last poll time.
func (m *Monitor) GetLastPollTime() time.Time {
	return m.lastPollTime
}
