// Package permission provides integration tests for the permission lifecycle management.
// This file contains property-based integration tests that verify the permission
// expiration notification workflow.
//
// Feature: autosso-refactoring, Property 18: 权限过期通知
// For any expired permission, the system should send an expiration notification to the user.
//
// **Validates: Requirements 10.2**
package permission

import (
	"context"
	"sync"
	"testing"
	"time"

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

	"pgregory.net/rapid"
)

// =============================================================================
// Mock Implementations for Lifecycle Integration Testing
// =============================================================================

// integrationLifecycleMockStateStore is a mock state store for lifecycle integration testing.
type integrationLifecycleMockStateStore struct {
	instances        map[string]*state.ApprovalInstance
	pendingRemovals  []*state.PendingRemoval
	deletedInstances map[string]bool
	mu               sync.Mutex
	nextRemovalID    int64
}

func newIntegrationLifecycleMockStateStore() *integrationLifecycleMockStateStore {
	return &integrationLifecycleMockStateStore{
		instances:        make(map[string]*state.ApprovalInstance),
		pendingRemovals:  make([]*state.PendingRemoval, 0),
		deletedInstances: make(map[string]bool),
		nextRemovalID:    1,
	}
}

func (s *integrationLifecycleMockStateStore) SaveInstance(ctx context.Context, instance *state.ApprovalInstance) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if instance != nil {
		s.instances[instance.ID] = instance
	}
	return nil
}

func (s *integrationLifecycleMockStateStore) GetInstance(ctx context.Context, instanceID string) (*state.ApprovalInstance, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.instances[instanceID], nil
}

func (s *integrationLifecycleMockStateStore) ListInstances(ctx context.Context, filter *state.InstanceFilter) ([]*state.ApprovalInstance, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	result := make([]*state.ApprovalInstance, 0, len(s.instances))
	for _, inst := range s.instances {
		// Apply filter if provided
		if filter != nil {
			if filter.Status != "" && inst.Status != filter.Status {
				continue
			}
			if filter.CloudType != "" && inst.CloudType != filter.CloudType {
				continue
			}
			if filter.Email != "" && inst.Email != filter.Email {
				continue
			}
		}
		result = append(result, inst)
	}
	return result, nil
}

func (s *integrationLifecycleMockStateStore) DeleteInstance(ctx context.Context, instanceID string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	delete(s.instances, instanceID)
	return nil
}

func (s *integrationLifecycleMockStateStore) SavePendingRemoval(ctx context.Context, removal *state.PendingRemoval) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if removal != nil {
		removal.ID = s.nextRemovalID
		s.nextRemovalID++
		s.pendingRemovals = append(s.pendingRemovals, removal)
	}
	return nil
}

func (s *integrationLifecycleMockStateStore) GetPendingRemovals(ctx context.Context) ([]*state.PendingRemoval, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	result := make([]*state.PendingRemoval, len(s.pendingRemovals))
	copy(result, s.pendingRemovals)
	return result, nil
}

func (s *integrationLifecycleMockStateStore) DeletePendingRemoval(ctx context.Context, id int64) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	for i, r := range s.pendingRemovals {
		if r.ID == id {
			s.pendingRemovals = append(s.pendingRemovals[:i], s.pendingRemovals[i+1:]...)
			break
		}
	}
	return nil
}

func (s *integrationLifecycleMockStateStore) SaveDeletedInstance(ctx context.Context, instanceID string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.deletedInstances[instanceID] = true
	return nil
}

func (s *integrationLifecycleMockStateStore) IsInstanceDeleted(ctx context.Context, instanceID string) (bool, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.deletedInstances[instanceID], nil
}

func (s *integrationLifecycleMockStateStore) Close() error {
	return nil
}

func (s *integrationLifecycleMockStateStore) getPendingRemovalsCount() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return len(s.pendingRemovals)
}

func (s *integrationLifecycleMockStateStore) hasPendingRemovalFor(accountID, email, permissionSet string) bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	for _, r := range s.pendingRemovals {
		if r.AccountID == accountID && r.Email == email && r.PermissionSet == permissionSet {
			return true
		}
	}
	return false
}

func (s *integrationLifecycleMockStateStore) getInstanceByID(id string) *state.ApprovalInstance {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.instances[id]
}

func (s *integrationLifecycleMockStateStore) getPendingRemovalsByEmail(email string) []*state.PendingRemoval {
	s.mu.Lock()
	defer s.mu.Unlock()
	var result []*state.PendingRemoval
	for _, r := range s.pendingRemovals {
		if r.Email == email {
			result = append(result, r)
		}
	}
	return result
}

// integrationLifecycleMockProvider is a mock provider for lifecycle integration testing.
type integrationLifecycleMockProvider struct {
	name                  string
	addPermissionCalls    []provider.PermissionRequest
	removePermissionCalls []provider.PermissionRequest
	mu                    sync.Mutex
	shouldFail            bool
}

func newIntegrationLifecycleMockProvider(name string) *integrationLifecycleMockProvider {
	return &integrationLifecycleMockProvider{
		name:                  name,
		addPermissionCalls:    make([]provider.PermissionRequest, 0),
		removePermissionCalls: make([]provider.PermissionRequest, 0),
	}
}

func (m *integrationLifecycleMockProvider) Name() string {
	return m.name
}

func (m *integrationLifecycleMockProvider) AddPermission(ctx context.Context, req *provider.PermissionRequest) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	if req != nil {
		m.addPermissionCalls = append(m.addPermissionCalls, *req)
	}
	if m.shouldFail {
		return context.DeadlineExceeded
	}
	return nil
}

func (m *integrationLifecycleMockProvider) RemovePermission(ctx context.Context, req *provider.PermissionRequest) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	if req != nil {
		m.removePermissionCalls = append(m.removePermissionCalls, *req)
	}
	if m.shouldFail {
		return context.DeadlineExceeded
	}
	return nil
}

func (m *integrationLifecycleMockProvider) GetUserID(ctx context.Context, email string) (string, error) {
	return "user-" + email, nil
}

func (m *integrationLifecycleMockProvider) ValidateCredentials(ctx context.Context) error {
	return nil
}

func (m *integrationLifecycleMockProvider) Close() error {
	return nil
}

// =============================================================================
// Generator Functions for Lifecycle Property-Based Testing
// =============================================================================

// genLifecycleEmail generates a valid email address for lifecycle testing.
func genLifecycleEmail() *rapid.Generator[string] {
	return rapid.Custom(func(t *rapid.T) string {
		user := rapid.StringMatching(`[a-z][a-z0-9]{2,10}`).Draw(t, "user")
		domain := rapid.SampledFrom([]string{"example.com", "test.org", "company.io", "pingcap.com"}).Draw(t, "domain")
		return user + "@" + domain
	})
}

// genLifecycleAccountID generates a valid cloud account ID based on provider type.
func genLifecycleAccountID(providerType provider.ProviderType) *rapid.Generator[string] {
	switch providerType {
	case provider.ProviderAWS:
		return rapid.StringMatching(`[0-9]{12}`)
	case provider.ProviderGCP:
		return rapid.StringMatching(`[a-z][a-z0-9-]{5,20}`)
	case provider.ProviderAzure:
		return rapid.StringMatching(`[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}`)
	case provider.ProviderAlibaba:
		return rapid.StringMatching(`[0-9]{16}`)
	default:
		return rapid.StringMatching(`[a-z0-9]{10,20}`)
	}
}

// genLifecyclePermissionSet generates a valid permission set name.
func genLifecyclePermissionSet() *rapid.Generator[string] {
	return rapid.OneOf(
		rapid.SampledFrom([]string{
			"DBaaS-Prod-ViewOnly-Role",
			"DBaaS-Prod-OnCall-Role",
			"DBaaS-Prod-Admin-Role",
			"DBaaS-Prod-Billing-Role",
			"PingCAP-SecurityAdmin-Role",
		}),
		rapid.StringMatching(`[A-Z][a-zA-Z0-9-]{5,30}-Role`),
	)
}

// genLifecyclePermissionSets generates a list of 1-5 permission sets.
func genLifecyclePermissionSets() *rapid.Generator[[]string] {
	return rapid.SliceOfN(genLifecyclePermissionSet(), 1, 5)
}

// genLifecycleProviderType generates a valid provider type.
func genLifecycleProviderType() *rapid.Generator[provider.ProviderType] {
	return rapid.SampledFrom([]provider.ProviderType{
		provider.ProviderAWS,
		provider.ProviderGCP,
		provider.ProviderAzure,
		provider.ProviderAlibaba,
	})
}

// genExpiredApprovalInstance generates an expired approval instance for testing.
func genExpiredApprovalInstance(providerType provider.ProviderType, accountID string) *rapid.Generator[*state.ApprovalInstance] {
	return rapid.Custom(func(t *rapid.T) *state.ApprovalInstance {
		// Generate end time in the past (1 hour to 7 days ago)
		hoursAgo := rapid.IntRange(1, 168).Draw(t, "hours_ago")
		endTime := time.Now().Add(-time.Duration(hoursAgo) * time.Hour)
		// Start time is before end time
		durationHours := rapid.IntRange(1, 720).Draw(t, "duration_hours")
		startTime := endTime.Add(-time.Duration(durationHours) * time.Hour)

		instanceID := rapid.StringMatching(`[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}`).Draw(t, "instance_id")

		return &state.ApprovalInstance{
			ID:             instanceID,
			Email:          genLifecycleEmail().Draw(t, "email"),
			AccountID:      accountID,
			PermissionSets: genLifecyclePermissionSets().Draw(t, "permission_sets"),
			StartTime:      startTime,
			EndTime:        endTime,
			Status:         state.StatusApproved,
			CloudType:      string(providerType),
			CreatedAt:      startTime,
			UpdatedAt:      time.Now(),
		}
	})
}

// genActiveApprovalInstance generates an active (non-expired) approval instance.
func genActiveApprovalInstance(providerType provider.ProviderType, accountID string) *rapid.Generator[*state.ApprovalInstance] {
	return rapid.Custom(func(t *rapid.T) *state.ApprovalInstance {
		startTime := time.Now().Add(-1 * time.Hour)
		// End time is in the future (1 hour to 30 days from now)
		hoursFromNow := rapid.IntRange(1, 720).Draw(t, "hours_from_now")
		endTime := time.Now().Add(time.Duration(hoursFromNow) * time.Hour)

		instanceID := rapid.StringMatching(`[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}`).Draw(t, "instance_id")

		return &state.ApprovalInstance{
			ID:             instanceID,
			Email:          genLifecycleEmail().Draw(t, "email"),
			AccountID:      accountID,
			PermissionSets: genLifecyclePermissionSets().Draw(t, "permission_sets"),
			StartTime:      startTime,
			EndTime:        endTime,
			Status:         state.StatusApproved,
			CloudType:      string(providerType),
			CreatedAt:      startTime,
			UpdatedAt:      time.Now(),
		}
	})
}

// =============================================================================
// Lifecycle Integration Test Setup Helpers
// =============================================================================

// lifecycleIntegrationTestSetup holds all components needed for lifecycle integration testing.
type lifecycleIntegrationTestSetup struct {
	manager    *Manager
	lifecycle  *Lifecycle
	registry   *provider.Registry
	stateStore *integrationLifecycleMockStateStore
	providers  map[provider.ProviderType]*integrationLifecycleMockProvider
	config     *config.Config
	logger     *logger.Logger
}

// createLifecycleIntegrationTestSetup creates a complete test setup for lifecycle testing.
// Note: This setup does not include a real notification service since we're testing
// the integration between Lifecycle, Manager, and StateStore. The notification
// behavior is verified by checking that pending removals are created correctly,
// which triggers the notification flow in the Manager.ScheduleRemoval method.
func createLifecycleIntegrationTestSetup(accounts map[provider.ProviderType]string) (*lifecycleIntegrationTestSetup, error) {
	// Create config with all accounts
	cfg := &config.Config{
		Feishu: config.FeishuConfig{
			ChatID: "test-chat-id",
		},
		Providers: config.ProvidersConfig{},
		Runtime: config.RuntimeConfig{
			GracePeriodDays:     3,
			HTTPTimeout:         10 * time.Second,
			MaxRetries:          3,
			ExpireCheckInterval: 1 * time.Hour,
		},
	}

	// Add accounts to config based on provider type
	for providerType, accountID := range accounts {
		account := config.CloudAccount{ID: accountID, Name: "test-" + accountID, Type: "prod"}
		switch providerType {
		case provider.ProviderAWS:
			cfg.Providers.AWS.Accounts = append(cfg.Providers.AWS.Accounts, account)
		case provider.ProviderGCP:
			cfg.Providers.GCP.Projects = append(cfg.Providers.GCP.Projects, account)
		case provider.ProviderAzure:
			cfg.Providers.Azure.Subscriptions = append(cfg.Providers.Azure.Subscriptions, account)
		case provider.ProviderAlibaba:
			cfg.Providers.Alibaba.Accounts = append(cfg.Providers.Alibaba.Accounts, account)
		}
	}

	// Create registry
	registry, err := provider.NewRegistry(cfg)
	if err != nil {
		return nil, err
	}

	// Create and register mock providers
	providers := make(map[provider.ProviderType]*integrationLifecycleMockProvider)
	for providerType := range accounts {
		mockProv := newIntegrationLifecycleMockProvider(string(providerType))
		providers[providerType] = mockProv
		if err := registry.RegisterProvider(providerType, mockProv); err != nil {
			return nil, err
		}
	}

	// Create logger
	log := logger.NewLogger(logger.Config{Level: logger.ErrorLevel})

	// Create state store
	stateStore := newIntegrationLifecycleMockStateStore()

	// Create manager without notification service (nil feishu client)
	// The notification behavior is tested by verifying pending removals are created
	manager := NewManager(registry, nil, stateStore, cfg, log)

	// Create lifecycle manager without notification service
	// We test the core logic of expiration detection and scheduling
	lifecycle := NewLifecycle(manager, nil, stateStore, cfg, log)

	return &lifecycleIntegrationTestSetup{
		manager:    manager,
		lifecycle:  lifecycle,
		registry:   registry,
		stateStore: stateStore,
		providers:  providers,
		config:     cfg,
		logger:     log,
	}, nil
}

// =============================================================================
// Property 18: 权限过期通知 - Integration Tests
// =============================================================================

// TestLifecycleIntegration_ExpiredPermissionSchedulesRemoval tests that for any
// expired permission, the system schedules removal (which triggers notification).
// The notification is sent as part of the ScheduleRemoval flow in the Manager.
//
// Feature: autosso-refactoring, Property 18: 权限过期通知
// **Validates: Requirements 10.2**
func TestLifecycleIntegration_ExpiredPermissionSchedulesRemoval(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate random provider type and account ID
		providerType := genLifecycleProviderType().Draw(rt, "provider_type")
		accountID := genLifecycleAccountID(providerType).Draw(rt, "account_id")

		// Create test setup
		accounts := map[provider.ProviderType]string{providerType: accountID}
		setup, err := createLifecycleIntegrationTestSetup(accounts)
		if err != nil {
			rt.Fatalf("Failed to create test setup: %v", err)
		}

		// Generate expired approval instance
		expiredInstance := genExpiredApprovalInstance(providerType, accountID).Draw(rt, "expired_instance")

		// Save the expired instance to state store
		ctx := context.Background()
		if err := setup.stateStore.SaveInstance(ctx, expiredInstance); err != nil {
			rt.Fatalf("Failed to save expired instance: %v", err)
		}

		// Verify no pending removals before processing
		initialCount := setup.stateStore.getPendingRemovalsCount()
		if initialCount != 0 {
			rt.Fatalf("Expected 0 pending removals before processing, got %d", initialCount)
		}

		// Process expired permissions (this should schedule removal and trigger notification)
		err = setup.lifecycle.ProcessExpiredPermissions(ctx)
		if err != nil {
			rt.Fatalf("ProcessExpiredPermissions failed: %v", err)
		}

		// Verify: Pending removals were created for each permission set
		// This indicates that ScheduleRemoval was called, which sends the notification
		for _, permSet := range expiredInstance.PermissionSets {
			if !setup.stateStore.hasPendingRemovalFor(accountID, expiredInstance.Email, permSet) {
				rt.Fatalf("Pending removal not created for permission set %q (notification would be sent)", permSet)
			}
		}

		// Verify: Total pending removals count matches permission sets count
		finalCount := setup.stateStore.getPendingRemovalsCount()
		if finalCount != len(expiredInstance.PermissionSets) {
			rt.Fatalf("Expected %d pending removals, got %d",
				len(expiredInstance.PermissionSets), finalCount)
		}
	})
}

// TestLifecycleIntegration_PendingRemovalContainsCorrectDetails tests that
// pending removal records contain correct user email and permission details.
// These details are used when sending the expiration notification.
//
// Feature: autosso-refactoring, Property 18: 权限过期通知
// **Validates: Requirements 10.2**
func TestLifecycleIntegration_PendingRemovalContainsCorrectDetails(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate random provider type and account ID
		providerType := genLifecycleProviderType().Draw(rt, "provider_type")
		accountID := genLifecycleAccountID(providerType).Draw(rt, "account_id")

		// Create test setup
		accounts := map[provider.ProviderType]string{providerType: accountID}
		setup, err := createLifecycleIntegrationTestSetup(accounts)
		if err != nil {
			rt.Fatalf("Failed to create test setup: %v", err)
		}

		// Generate expired approval instance with specific details
		email := genLifecycleEmail().Draw(rt, "email")
		permissionSets := genLifecyclePermissionSets().Draw(rt, "permission_sets")
		endTime := time.Now().Add(-1 * time.Hour) // Expired 1 hour ago

		expiredInstance := &state.ApprovalInstance{
			ID:             rapid.StringMatching(`[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}`).Draw(rt, "instance_id"),
			Email:          email,
			AccountID:      accountID,
			PermissionSets: permissionSets,
			StartTime:      endTime.Add(-24 * time.Hour),
			EndTime:        endTime,
			Status:         state.StatusApproved,
			CloudType:      string(providerType),
			CreatedAt:      endTime.Add(-24 * time.Hour),
			UpdatedAt:      time.Now(),
		}

		// Save the expired instance
		ctx := context.Background()
		if err := setup.stateStore.SaveInstance(ctx, expiredInstance); err != nil {
			rt.Fatalf("Failed to save expired instance: %v", err)
		}

		// Process expired permissions
		err = setup.lifecycle.ProcessExpiredPermissions(ctx)
		if err != nil {
			rt.Fatalf("ProcessExpiredPermissions failed: %v", err)
		}

		// Verify: Pending removals contain correct details
		removals := setup.stateStore.getPendingRemovalsByEmail(email)
		if len(removals) != len(permissionSets) {
			rt.Fatalf("Expected %d pending removals for email %s, got %d",
				len(permissionSets), email, len(removals))
		}

		// Verify each pending removal has correct details
		for _, removal := range removals {
			// Verify email is correct
			if removal.Email != email {
				rt.Fatalf("Email mismatch: expected %q, got %q", email, removal.Email)
			}

			// Verify cloud type is correct
			if removal.CloudType != string(providerType) {
				rt.Fatalf("CloudType mismatch: expected %q, got %q", providerType, removal.CloudType)
			}

			// Verify account ID is correct
			if removal.AccountID != accountID {
				rt.Fatalf("AccountID mismatch: expected %q, got %q", accountID, removal.AccountID)
			}

			// Verify scheduled time is after expired time (grace period applied)
			if !removal.ScheduledAt.After(removal.ExpiredAt) {
				rt.Fatalf("ScheduledAt should be after ExpiredAt")
			}
		}
	})
}

// TestLifecycleIntegration_NotificationSentAtRightTime tests that notifications
// are sent when permission expires (not before). Active permissions should not
// trigger any notification or pending removal.
//
// Feature: autosso-refactoring, Property 18: 权限过期通知
// **Validates: Requirements 10.2**
func TestLifecycleIntegration_NotificationSentAtRightTime(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate random provider type and account ID
		providerType := genLifecycleProviderType().Draw(rt, "provider_type")
		accountID := genLifecycleAccountID(providerType).Draw(rt, "account_id")

		// Create test setup
		accounts := map[provider.ProviderType]string{providerType: accountID}
		setup, err := createLifecycleIntegrationTestSetup(accounts)
		if err != nil {
			rt.Fatalf("Failed to create test setup: %v", err)
		}

		ctx := context.Background()

		// Create an active (non-expired) instance
		activeInstance := genActiveApprovalInstance(providerType, accountID).Draw(rt, "active_instance")
		if err := setup.stateStore.SaveInstance(ctx, activeInstance); err != nil {
			rt.Fatalf("Failed to save active instance: %v", err)
		}

		// Process expired permissions - should not create pending removal for active instance
		err = setup.lifecycle.ProcessExpiredPermissions(ctx)
		if err != nil {
			rt.Fatalf("ProcessExpiredPermissions failed: %v", err)
		}

		// Verify: No pending removal for active instance
		for _, permSet := range activeInstance.PermissionSets {
			if setup.stateStore.hasPendingRemovalFor(accountID, activeInstance.Email, permSet) {
				rt.Fatalf("Pending removal should not be created for active (non-expired) instance")
			}
		}

		// Now create an expired instance
		expiredInstance := genExpiredApprovalInstance(providerType, accountID).Draw(rt, "expired_instance")
		if err := setup.stateStore.SaveInstance(ctx, expiredInstance); err != nil {
			rt.Fatalf("Failed to save expired instance: %v", err)
		}

		// Process expired permissions again
		err = setup.lifecycle.ProcessExpiredPermissions(ctx)
		if err != nil {
			rt.Fatalf("ProcessExpiredPermissions failed: %v", err)
		}

		// Verify: Pending removal created for expired instance
		for _, permSet := range expiredInstance.PermissionSets {
			if !setup.stateStore.hasPendingRemovalFor(accountID, expiredInstance.Email, permSet) {
				rt.Fatalf("Pending removal should be created for expired instance")
			}
		}
	})
}

// TestLifecycleIntegration_MultipleExpiredPermissionsSameUser tests that
// multiple expired permissions for the same user are handled correctly.
// Each permission set should create a separate pending removal.
//
// Feature: autosso-refactoring, Property 18: 权限过期通知
// **Validates: Requirements 10.2**
func TestLifecycleIntegration_MultipleExpiredPermissionsSameUser(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate random provider type and account ID
		providerType := genLifecycleProviderType().Draw(rt, "provider_type")
		accountID := genLifecycleAccountID(providerType).Draw(rt, "account_id")

		// Create test setup
		accounts := map[provider.ProviderType]string{providerType: accountID}
		setup, err := createLifecycleIntegrationTestSetup(accounts)
		if err != nil {
			rt.Fatalf("Failed to create test setup: %v", err)
		}

		ctx := context.Background()

		// Generate a common email for multiple instances
		email := genLifecycleEmail().Draw(rt, "email")

		// Create multiple expired instances for the same user
		numInstances := rapid.IntRange(2, 5).Draw(rt, "num_instances")
		totalPermissionSets := 0

		for i := 0; i < numInstances; i++ {
			hoursAgo := rapid.IntRange(1, 168).Draw(rt, "hours_ago")
			endTime := time.Now().Add(-time.Duration(hoursAgo) * time.Hour)
			startTime := endTime.Add(-24 * time.Hour)
			permissionSets := genLifecyclePermissionSets().Draw(rt, "permission_sets")
			totalPermissionSets += len(permissionSets)

			instance := &state.ApprovalInstance{
				ID:             rapid.StringMatching(`[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}`).Draw(rt, "instance_id"),
				Email:          email,
				AccountID:      accountID,
				PermissionSets: permissionSets,
				StartTime:      startTime,
				EndTime:        endTime,
				Status:         state.StatusApproved,
				CloudType:      string(providerType),
				CreatedAt:      startTime,
				UpdatedAt:      time.Now(),
			}

			if err := setup.stateStore.SaveInstance(ctx, instance); err != nil {
				rt.Fatalf("Failed to save instance %d: %v", i, err)
			}
		}

		// Process expired permissions
		err = setup.lifecycle.ProcessExpiredPermissions(ctx)
		if err != nil {
			rt.Fatalf("ProcessExpiredPermissions failed: %v", err)
		}

		// Verify: Pending removals were created for all permission sets
		removals := setup.stateStore.getPendingRemovalsByEmail(email)
		if len(removals) != totalPermissionSets {
			rt.Fatalf("Expected %d pending removals for user %s, got %d",
				totalPermissionSets, email, len(removals))
		}
	})
}

// TestLifecycleIntegration_AllCloudPlatformsExpirationNotification tests that
// expiration notifications work correctly for all supported cloud platforms.
//
// Feature: autosso-refactoring, Property 18: 权限过期通知
// **Validates: Requirements 10.2**
func TestLifecycleIntegration_AllCloudPlatformsExpirationNotification(t *testing.T) {
	allProviderTypes := []provider.ProviderType{
		provider.ProviderAWS,
		provider.ProviderGCP,
		provider.ProviderAzure,
		provider.ProviderAlibaba,
	}

	rapid.Check(t, func(rt *rapid.T) {
		// Generate account IDs for all provider types
		accounts := make(map[provider.ProviderType]string)
		for _, pt := range allProviderTypes {
			accounts[pt] = genLifecycleAccountID(pt).Draw(rt, "account_"+string(pt))
		}

		// Create test setup with all providers
		setup, err := createLifecycleIntegrationTestSetup(accounts)
		if err != nil {
			rt.Fatalf("Failed to create test setup: %v", err)
		}

		ctx := context.Background()

		// Create expired instances for each cloud platform
		expiredInstances := make(map[provider.ProviderType]*state.ApprovalInstance)
		for _, providerType := range allProviderTypes {
			accountID := accounts[providerType]
			expiredInstance := genExpiredApprovalInstance(providerType, accountID).Draw(rt, "expired_"+string(providerType))
			expiredInstances[providerType] = expiredInstance

			if err := setup.stateStore.SaveInstance(ctx, expiredInstance); err != nil {
				rt.Fatalf("Failed to save expired instance for %s: %v", providerType, err)
			}
		}

		// Process expired permissions
		err = setup.lifecycle.ProcessExpiredPermissions(ctx)
		if err != nil {
			rt.Fatalf("ProcessExpiredPermissions failed: %v", err)
		}

		// Verify: Pending removals created for all cloud platforms
		for _, providerType := range allProviderTypes {
			expiredInstance := expiredInstances[providerType]
			accountID := accounts[providerType]

			for _, permSet := range expiredInstance.PermissionSets {
				if !setup.stateStore.hasPendingRemovalFor(accountID, expiredInstance.Email, permSet) {
					rt.Fatalf("Pending removal not found for %s platform, user %s, permission %s",
						providerType, expiredInstance.Email, permSet)
				}
			}
		}
	})
}

// TestLifecycleIntegration_InstanceStatusUpdatedToExpired tests that the
// instance status is updated to expired after processing.
//
// Feature: autosso-refactoring, Property 18: 权限过期通知
// **Validates: Requirements 10.2**
func TestLifecycleIntegration_InstanceStatusUpdatedToExpired(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate random provider type and account ID
		providerType := genLifecycleProviderType().Draw(rt, "provider_type")
		accountID := genLifecycleAccountID(providerType).Draw(rt, "account_id")

		// Create test setup
		accounts := map[provider.ProviderType]string{providerType: accountID}
		setup, err := createLifecycleIntegrationTestSetup(accounts)
		if err != nil {
			rt.Fatalf("Failed to create test setup: %v", err)
		}

		ctx := context.Background()

		// Generate expired approval instance
		expiredInstance := genExpiredApprovalInstance(providerType, accountID).Draw(rt, "expired_instance")

		// Verify initial status is approved
		if expiredInstance.Status != state.StatusApproved {
			rt.Fatalf("Initial status should be %q, got %q", state.StatusApproved, expiredInstance.Status)
		}

		// Save the expired instance
		if err := setup.stateStore.SaveInstance(ctx, expiredInstance); err != nil {
			rt.Fatalf("Failed to save expired instance: %v", err)
		}

		// Process expired permissions
		err = setup.lifecycle.ProcessExpiredPermissions(ctx)
		if err != nil {
			rt.Fatalf("ProcessExpiredPermissions failed: %v", err)
		}

		// Verify: Instance status updated to expired
		updatedInstance := setup.stateStore.getInstanceByID(expiredInstance.ID)
		if updatedInstance == nil {
			rt.Fatalf("Instance %s not found after processing", expiredInstance.ID)
		}
		if updatedInstance.Status != state.StatusExpired {
			rt.Fatalf("Instance status should be %q after processing, got %q",
				state.StatusExpired, updatedInstance.Status)
		}
	})
}

// TestLifecycleIntegration_AlreadyExpiredInstanceNotReprocessed tests that
// instances already marked as expired are not reprocessed.
//
// Feature: autosso-refactoring, Property 18: 权限过期通知
// **Validates: Requirements 10.2**
func TestLifecycleIntegration_AlreadyExpiredInstanceNotReprocessed(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate random provider type and account ID
		providerType := genLifecycleProviderType().Draw(rt, "provider_type")
		accountID := genLifecycleAccountID(providerType).Draw(rt, "account_id")

		// Create test setup
		accounts := map[provider.ProviderType]string{providerType: accountID}
		setup, err := createLifecycleIntegrationTestSetup(accounts)
		if err != nil {
			rt.Fatalf("Failed to create test setup: %v", err)
		}

		ctx := context.Background()

		// Generate expired approval instance but set status to already expired
		expiredInstance := genExpiredApprovalInstance(providerType, accountID).Draw(rt, "expired_instance")
		expiredInstance.Status = state.StatusExpired // Already marked as expired

		// Save the instance
		if err := setup.stateStore.SaveInstance(ctx, expiredInstance); err != nil {
			rt.Fatalf("Failed to save expired instance: %v", err)
		}

		// Process expired permissions
		err = setup.lifecycle.ProcessExpiredPermissions(ctx)
		if err != nil {
			rt.Fatalf("ProcessExpiredPermissions failed: %v", err)
		}

		// Verify: No pending removals created (already processed)
		if setup.stateStore.getPendingRemovalsCount() != 0 {
			rt.Fatal("No pending removals should be created for already expired instance")
		}
	})
}

// TestLifecycleIntegration_GracePeriodAppliedCorrectly tests that the
// grace period is correctly applied to the scheduled removal time.
//
// Feature: autosso-refactoring, Property 18: 权限过期通知
// **Validates: Requirements 10.2**
func TestLifecycleIntegration_GracePeriodAppliedCorrectly(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate random provider type and account ID
		providerType := genLifecycleProviderType().Draw(rt, "provider_type")
		accountID := genLifecycleAccountID(providerType).Draw(rt, "account_id")

		// Generate random grace period (1-7 days)
		gracePeriodDays := rapid.IntRange(1, 7).Draw(rt, "grace_period_days")

		// Create test setup with custom grace period
		accounts := map[provider.ProviderType]string{providerType: accountID}
		setup, err := createLifecycleIntegrationTestSetup(accounts)
		if err != nil {
			rt.Fatalf("Failed to create test setup: %v", err)
		}

		// Update grace period in config
		setup.config.Runtime.GracePeriodDays = gracePeriodDays

		ctx := context.Background()

		// Generate expired approval instance with known end time
		endTime := time.Now().Add(-1 * time.Hour) // Expired 1 hour ago
		expiredInstance := &state.ApprovalInstance{
			ID:             rapid.StringMatching(`[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}`).Draw(rt, "instance_id"),
			Email:          genLifecycleEmail().Draw(rt, "email"),
			AccountID:      accountID,
			PermissionSets: []string{genLifecyclePermissionSet().Draw(rt, "permission_set")},
			StartTime:      endTime.Add(-24 * time.Hour),
			EndTime:        endTime,
			Status:         state.StatusApproved,
			CloudType:      string(providerType),
			CreatedAt:      endTime.Add(-24 * time.Hour),
			UpdatedAt:      time.Now(),
		}

		// Save the expired instance
		if err := setup.stateStore.SaveInstance(ctx, expiredInstance); err != nil {
			rt.Fatalf("Failed to save expired instance: %v", err)
		}

		// Process expired permissions
		err = setup.lifecycle.ProcessExpiredPermissions(ctx)
		if err != nil {
			rt.Fatalf("ProcessExpiredPermissions failed: %v", err)
		}

		// Verify: Pending removal has correct scheduled time
		removals := setup.stateStore.getPendingRemovalsByEmail(expiredInstance.Email)
		if len(removals) != 1 {
			rt.Fatalf("Expected 1 pending removal, got %d", len(removals))
		}

		removal := removals[0]
		expectedScheduledTime := endTime.Add(time.Duration(gracePeriodDays) * 24 * time.Hour)

		// Allow 1 minute tolerance for time comparison
		timeDiff := removal.ScheduledAt.Sub(expectedScheduledTime)
		if timeDiff < -time.Minute || timeDiff > time.Minute {
			rt.Fatalf("ScheduledAt mismatch: expected around %v, got %v (diff: %v)",
				expectedScheduledTime, removal.ScheduledAt, timeDiff)
		}
	})
}

// TestLifecycleIntegration_ConcurrentExpirationProcessing tests that concurrent
// expiration processing is handled correctly without data races.
//
// Feature: autosso-refactoring, Property 18: 权限过期通知
// **Validates: Requirements 10.2**
func TestLifecycleIntegration_ConcurrentExpirationProcessing(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate random provider type and account ID
		providerType := genLifecycleProviderType().Draw(rt, "provider_type")
		accountID := genLifecycleAccountID(providerType).Draw(rt, "account_id")

		// Create test setup
		accounts := map[provider.ProviderType]string{providerType: accountID}
		setup, err := createLifecycleIntegrationTestSetup(accounts)
		if err != nil {
			rt.Fatalf("Failed to create test setup: %v", err)
		}

		ctx := context.Background()

		// Create multiple expired instances
		numInstances := rapid.IntRange(3, 10).Draw(rt, "num_instances")
		for i := 0; i < numInstances; i++ {
			expiredInstance := genExpiredApprovalInstance(providerType, accountID).Draw(rt, "expired_instance")
			if err := setup.stateStore.SaveInstance(ctx, expiredInstance); err != nil {
				rt.Fatalf("Failed to save expired instance %d: %v", i, err)
			}
		}

		// Process expired permissions concurrently
		numConcurrent := rapid.IntRange(2, 5).Draw(rt, "num_concurrent")
		var wg sync.WaitGroup
		errors := make([]error, numConcurrent)

		for i := 0; i < numConcurrent; i++ {
			wg.Add(1)
			go func(idx int) {
				defer wg.Done()
				errors[idx] = setup.lifecycle.ProcessExpiredPermissions(ctx)
			}(i)
		}

		wg.Wait()

		// Verify: No panics or data races occurred
		// Some operations may fail due to concurrent access, but no crashes
		for i, err := range errors {
			if err != nil {
				t.Logf("Concurrent operation %d returned error: %v", i, err)
			}
		}

		// Verify: At least some pending removals were created
		if setup.stateStore.getPendingRemovalsCount() == 0 {
			rt.Fatal("Expected at least some pending removals from concurrent processing")
		}
	})
}

// TestLifecycleIntegration_ContextCancellationHandled tests that context
// cancellation is properly handled during expiration processing.
//
// Feature: autosso-refactoring, Property 18: 权限过期通知
// **Validates: Requirements 10.2**
func TestLifecycleIntegration_ContextCancellationHandled(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate random provider type and account ID
		providerType := genLifecycleProviderType().Draw(rt, "provider_type")
		accountID := genLifecycleAccountID(providerType).Draw(rt, "account_id")

		// Create test setup
		accounts := map[provider.ProviderType]string{providerType: accountID}
		setup, err := createLifecycleIntegrationTestSetup(accounts)
		if err != nil {
			rt.Fatalf("Failed to create test setup: %v", err)
		}

		// Generate expired approval instance
		expiredInstance := genExpiredApprovalInstance(providerType, accountID).Draw(rt, "expired_instance")

		// Save the expired instance
		ctx := context.Background()
		if err := setup.stateStore.SaveInstance(ctx, expiredInstance); err != nil {
			rt.Fatalf("Failed to save expired instance: %v", err)
		}

		// Create a cancelled context
		cancelledCtx, cancel := context.WithCancel(context.Background())
		cancel() // Cancel immediately

		// Process expired permissions with cancelled context
		err = setup.lifecycle.ProcessExpiredPermissions(cancelledCtx)

		// The operation should either return context.Canceled or complete
		// before checking context - both are acceptable behaviors
		if err != nil && err != context.Canceled {
			// Log but don't fail - implementation may vary
			t.Logf("ProcessExpiredPermissions with cancelled context returned: %v", err)
		}
	})
}

// TestLifecycleIntegration_MixedExpiredAndActiveInstances tests that only
// expired instances trigger notifications, not active ones.
//
// Feature: autosso-refactoring, Property 18: 权限过期通知
// **Validates: Requirements 10.2**
func TestLifecycleIntegration_MixedExpiredAndActiveInstances(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate random provider type and account ID
		providerType := genLifecycleProviderType().Draw(rt, "provider_type")
		accountID := genLifecycleAccountID(providerType).Draw(rt, "account_id")

		// Create test setup
		accounts := map[provider.ProviderType]string{providerType: accountID}
		setup, err := createLifecycleIntegrationTestSetup(accounts)
		if err != nil {
			rt.Fatalf("Failed to create test setup: %v", err)
		}

		ctx := context.Background()

		// Create a mix of expired and active instances
		numExpired := rapid.IntRange(1, 5).Draw(rt, "num_expired")
		numActive := rapid.IntRange(1, 5).Draw(rt, "num_active")

		expiredEmails := make(map[string]int) // email -> total permission sets
		activeEmails := make(map[string]bool)

		// Create expired instances
		for i := 0; i < numExpired; i++ {
			expiredInstance := genExpiredApprovalInstance(providerType, accountID).Draw(rt, "expired_instance")
			expiredEmails[expiredInstance.Email] += len(expiredInstance.PermissionSets)
			if err := setup.stateStore.SaveInstance(ctx, expiredInstance); err != nil {
				rt.Fatalf("Failed to save expired instance %d: %v", i, err)
			}
		}

		// Create active instances
		for i := 0; i < numActive; i++ {
			activeInstance := genActiveApprovalInstance(providerType, accountID).Draw(rt, "active_instance")
			activeEmails[activeInstance.Email] = true
			if err := setup.stateStore.SaveInstance(ctx, activeInstance); err != nil {
				rt.Fatalf("Failed to save active instance %d: %v", i, err)
			}
		}

		// Process expired permissions
		err = setup.lifecycle.ProcessExpiredPermissions(ctx)
		if err != nil {
			rt.Fatalf("ProcessExpiredPermissions failed: %v", err)
		}

		// Verify: Pending removals created only for expired instances
		removals, _ := setup.stateStore.GetPendingRemovals(ctx)

		for _, removal := range removals {
			// Check that removal is not for an active-only email
			if activeEmails[removal.Email] && expiredEmails[removal.Email] == 0 {
				rt.Fatalf("Pending removal should not be created for active instance (email: %s)", removal.Email)
			}
		}

		// Verify: All expired instances have pending removals
		for email, expectedCount := range expiredEmails {
			actualCount := 0
			for _, removal := range removals {
				if removal.Email == email {
					actualCount++
				}
			}
			if actualCount != expectedCount {
				rt.Fatalf("Expected %d pending removals for expired email %s, got %d",
					expectedCount, email, actualCount)
			}
		}
	})
}
