/*
Package main contains property-based tests for graceful shutdown functionality.

Feature: autosso-refactoring, Property 14: 优雅关闭
*对于任意* 终止信号，系统应等待所有正在进行的操作完成后再退出。

**Validates: Requirements 6.7**
*/
package main

import (
	"context"
	"fmt"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"AutoSSO/internal/pkg/utils"

	"pgregory.net/rapid"
)

// TestableApplication is a simplified version of Application for testing shutdown behavior.
// It provides the same shutdown semantics without requiring full initialization.
type TestableApplication struct {
	wg              sync.WaitGroup
	shutdownTimeout time.Duration
	resourcesClosed atomic.Bool
	goroutineCount  atomic.Int32
}

// NewTestableApplication creates a new testable application instance.
func NewTestableApplication(shutdownTimeout time.Duration) *TestableApplication {
	return &TestableApplication{
		shutdownTimeout: shutdownTimeout,
	}
}

// startGoroutine starts a goroutine with WaitGroup tracking, similar to Application.startGoroutine.
func (app *TestableApplication) startGoroutine(ctx context.Context, name string, fn func(context.Context) error) {
	app.wg.Add(1)
	app.goroutineCount.Add(1)
	go func() {
		defer app.wg.Done()
		defer app.goroutineCount.Add(-1)
		defer utils.RecoverWithHandler(name, func(info *utils.PanicInfo) {
			// Panic recovered, goroutine still completes
		})

		_ = fn(ctx)
	}()
}

// Shutdown performs graceful shutdown, waiting for all goroutines to complete.
func (app *TestableApplication) Shutdown() error {
	done := make(chan struct{})
	go func() {
		defer utils.RecoverWithHandler("shutdown-waiter", nil)
		app.wg.Wait()
		close(done)
	}()

	select {
	case <-done:
		app.resourcesClosed.Store(true)
		return nil
	case <-time.After(app.shutdownTimeout):
		return fmt.Errorf("shutdown timeout exceeded after %v", app.shutdownTimeout)
	}
}

// TestProperty14_GracefulShutdown_WaitsForAllGoroutines tests that shutdown waits for all goroutines.
//
// Feature: autosso-refactoring, Property 14: 优雅关闭
// **Validates: Requirements 6.7**
func TestProperty14_GracefulShutdown_WaitsForAllGoroutines(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate number of goroutines (1-20)
		numGoroutines := rapid.IntRange(1, 20).Draw(rt, "numGoroutines")
		// Generate work duration per goroutine (1-50ms)
		workDurationMs := rapid.IntRange(1, 50).Draw(rt, "workDurationMs")

		app := NewTestableApplication(5 * time.Second)
		ctx, cancel := context.WithCancel(context.Background())

		var completedCount atomic.Int32

		// Start goroutines that do some work
		for i := 0; i < numGoroutines; i++ {
			app.startGoroutine(ctx, fmt.Sprintf("worker-%d", i), func(ctx context.Context) error {
				// Simulate work
				select {
				case <-time.After(time.Duration(workDurationMs) * time.Millisecond):
					completedCount.Add(1)
				case <-ctx.Done():
					// Even on cancellation, mark as completed after brief cleanup
					time.Sleep(time.Duration(workDurationMs/2) * time.Millisecond)
					completedCount.Add(1)
				}
				return nil
			})
		}

		// Cancel context to signal shutdown
		cancel()

		// Perform shutdown
		err := app.Shutdown()

		// Property: Shutdown should succeed (not timeout)
		if err != nil {
			rt.Fatalf("Shutdown failed:\n"+
				"Error: %v\n"+
				"Num goroutines: %d\n"+
				"Work duration: %dms", err, numGoroutines, workDurationMs)
		}

		// Property: All goroutines should have completed
		if int(completedCount.Load()) != numGoroutines {
			rt.Fatalf("Not all goroutines completed:\n"+
				"Expected: %d\n"+
				"Got: %d", numGoroutines, completedCount.Load())
		}

		// Property: Goroutine count should be zero after shutdown
		if app.goroutineCount.Load() != 0 {
			rt.Fatalf("Goroutine count not zero after shutdown:\n"+
				"Count: %d", app.goroutineCount.Load())
		}
	})
}

// TestProperty14_GracefulShutdown_TimeoutRespected tests that shutdown timeout is respected.
//
// Feature: autosso-refactoring, Property 14: 优雅关闭
// **Validates: Requirements 6.7**
func TestProperty14_GracefulShutdown_TimeoutRespected(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate a short timeout (10-100ms)
		timeoutMs := rapid.IntRange(10, 100).Draw(rt, "timeoutMs")
		// Work duration longer than timeout
		workDurationMs := timeoutMs * 3

		app := NewTestableApplication(time.Duration(timeoutMs) * time.Millisecond)
		ctx := context.Background()

		// Start a goroutine that takes longer than the timeout
		app.startGoroutine(ctx, "slow-worker", func(ctx context.Context) error {
			time.Sleep(time.Duration(workDurationMs) * time.Millisecond)
			return nil
		})

		// Measure shutdown time
		start := time.Now()
		err := app.Shutdown()
		elapsed := time.Since(start)

		// Property: Shutdown should return timeout error
		if err == nil {
			rt.Fatalf("Shutdown should have timed out:\n"+
				"Timeout: %dms\n"+
				"Work duration: %dms", timeoutMs, workDurationMs)
		}

		// Property: Shutdown should not wait longer than timeout + small buffer
		maxWait := time.Duration(timeoutMs)*time.Millisecond + 50*time.Millisecond
		if elapsed > maxWait {
			rt.Fatalf("Shutdown waited too long:\n"+
				"Expected max: %v\n"+
				"Actual: %v", maxWait, elapsed)
		}

		// Property: Shutdown should wait at least the timeout duration
		minWait := time.Duration(timeoutMs) * time.Millisecond
		if elapsed < minWait {
			rt.Fatalf("Shutdown returned too early:\n"+
				"Expected min: %v\n"+
				"Actual: %v", minWait, elapsed)
		}
	})
}

// TestProperty14_GracefulShutdown_ResourcesClosedAfterShutdown tests that resources are closed after shutdown.
//
// Feature: autosso-refactoring, Property 14: 优雅关闭
// **Validates: Requirements 6.7**
func TestProperty14_GracefulShutdown_ResourcesClosedAfterShutdown(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate number of goroutines (1-10)
		numGoroutines := rapid.IntRange(1, 10).Draw(rt, "numGoroutines")

		app := NewTestableApplication(5 * time.Second)
		ctx, cancel := context.WithCancel(context.Background())

		// Start goroutines
		for i := 0; i < numGoroutines; i++ {
			app.startGoroutine(ctx, fmt.Sprintf("worker-%d", i), func(ctx context.Context) error {
				<-ctx.Done()
				return nil
			})
		}

		// Property: Resources should not be closed before shutdown
		if app.resourcesClosed.Load() {
			rt.Fatal("Resources were closed before shutdown")
		}

		// Cancel and shutdown
		cancel()
		err := app.Shutdown()

		// Property: Shutdown should succeed
		if err != nil {
			rt.Fatalf("Shutdown failed: %v", err)
		}

		// Property: Resources should be closed after successful shutdown
		if !app.resourcesClosed.Load() {
			rt.Fatal("Resources were not closed after shutdown")
		}
	})
}

// TestProperty14_GracefulShutdown_ContextCancellationPropagates tests that context cancellation propagates to goroutines.
//
// Feature: autosso-refactoring, Property 14: 优雅关闭
// **Validates: Requirements 6.7**
func TestProperty14_GracefulShutdown_ContextCancellationPropagates(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate number of goroutines (1-15)
		numGoroutines := rapid.IntRange(1, 15).Draw(rt, "numGoroutines")

		app := NewTestableApplication(5 * time.Second)
		ctx, cancel := context.WithCancel(context.Background())

		var cancelledCount atomic.Int32

		// Start goroutines that wait for context cancellation
		for i := 0; i < numGoroutines; i++ {
			app.startGoroutine(ctx, fmt.Sprintf("worker-%d", i), func(ctx context.Context) error {
				<-ctx.Done()
				cancelledCount.Add(1)
				return ctx.Err()
			})
		}

		// Give goroutines time to start
		time.Sleep(10 * time.Millisecond)

		// Cancel context
		cancel()

		// Perform shutdown
		err := app.Shutdown()

		// Property: Shutdown should succeed
		if err != nil {
			rt.Fatalf("Shutdown failed: %v", err)
		}

		// Property: All goroutines should have received cancellation
		if int(cancelledCount.Load()) != numGoroutines {
			rt.Fatalf("Not all goroutines received cancellation:\n"+
				"Expected: %d\n"+
				"Got: %d", numGoroutines, cancelledCount.Load())
		}
	})
}

// TestProperty14_GracefulShutdown_PanicInGoroutineDoesNotBlockShutdown tests that panics don't block shutdown.
//
// Feature: autosso-refactoring, Property 14: 优雅关闭
// **Validates: Requirements 6.7**
func TestProperty14_GracefulShutdown_PanicInGoroutineDoesNotBlockShutdown(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate number of goroutines (2-10)
		numGoroutines := rapid.IntRange(2, 10).Draw(rt, "numGoroutines")
		// Generate which goroutine will panic (0 to numGoroutines-1)
		panicIndex := rapid.IntRange(0, numGoroutines-1).Draw(rt, "panicIndex")
		// Generate panic message
		panicMsg := rapid.String().Draw(rt, "panicMsg")

		app := NewTestableApplication(5 * time.Second)
		ctx, cancel := context.WithCancel(context.Background())

		var completedCount atomic.Int32

		// Start goroutines, one of which will panic
		for i := 0; i < numGoroutines; i++ {
			idx := i
			app.startGoroutine(ctx, fmt.Sprintf("worker-%d", i), func(ctx context.Context) error {
				if idx == panicIndex {
					panic(panicMsg)
				}
				<-ctx.Done()
				completedCount.Add(1)
				return nil
			})
		}

		// Give goroutines time to start and panic
		time.Sleep(10 * time.Millisecond)

		// Cancel and shutdown
		cancel()
		err := app.Shutdown()

		// Property: Shutdown should succeed despite panic
		if err != nil {
			rt.Fatalf("Shutdown failed despite panic recovery:\n"+
				"Error: %v\n"+
				"Panic index: %d", err, panicIndex)
		}

		// Property: Non-panicking goroutines should complete
		expectedCompleted := numGoroutines - 1 // One panicked
		if int(completedCount.Load()) != expectedCompleted {
			rt.Fatalf("Non-panicking goroutines did not complete:\n"+
				"Expected: %d\n"+
				"Got: %d", expectedCompleted, completedCount.Load())
		}
	})
}

// TestProperty14_GracefulShutdown_EmptyApplication tests shutdown with no goroutines.
//
// Feature: autosso-refactoring, Property 14: 优雅关闭
// **Validates: Requirements 6.7**
func TestProperty14_GracefulShutdown_EmptyApplication(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate timeout (10-500ms)
		timeoutMs := rapid.IntRange(10, 500).Draw(rt, "timeoutMs")

		app := NewTestableApplication(time.Duration(timeoutMs) * time.Millisecond)

		// Shutdown with no goroutines
		start := time.Now()
		err := app.Shutdown()
		elapsed := time.Since(start)

		// Property: Shutdown should succeed immediately
		if err != nil {
			rt.Fatalf("Shutdown failed with no goroutines: %v", err)
		}

		// Property: Shutdown should be fast (< 50ms)
		if elapsed > 50*time.Millisecond {
			rt.Fatalf("Shutdown took too long with no goroutines:\n"+
				"Elapsed: %v", elapsed)
		}

		// Property: Resources should be closed
		if !app.resourcesClosed.Load() {
			rt.Fatal("Resources were not closed after shutdown")
		}
	})
}

// TestProperty14_GracefulShutdown_ConcurrentShutdownCalls tests that concurrent shutdown calls are safe.
//
// Feature: autosso-refactoring, Property 14: 优雅关闭
// **Validates: Requirements 6.7**
func TestProperty14_GracefulShutdown_ConcurrentShutdownCalls(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate number of concurrent shutdown calls (2-5)
		numCalls := rapid.IntRange(2, 5).Draw(rt, "numCalls")

		app := NewTestableApplication(5 * time.Second)
		ctx, cancel := context.WithCancel(context.Background())

		// Start a goroutine
		app.startGoroutine(ctx, "worker", func(ctx context.Context) error {
			<-ctx.Done()
			return nil
		})

		// Cancel context
		cancel()

		// Make concurrent shutdown calls
		var wg sync.WaitGroup
		var successCount atomic.Int32
		var errorCount atomic.Int32

		for i := 0; i < numCalls; i++ {
			wg.Add(1)
			go func() {
				defer wg.Done()
				err := app.Shutdown()
				if err == nil {
					successCount.Add(1)
				} else {
					errorCount.Add(1)
				}
			}()
		}

		wg.Wait()

		// Property: At least one shutdown call should succeed
		if successCount.Load() == 0 {
			rt.Fatalf("No shutdown calls succeeded:\n"+
				"Num calls: %d\n"+
				"Errors: %d", numCalls, errorCount.Load())
		}

		// Property: Resources should be closed
		if !app.resourcesClosed.Load() {
			rt.Fatal("Resources were not closed after concurrent shutdowns")
		}
	})
}

// TestProperty14_GracefulShutdown_VariableWorkDurations tests shutdown with varying work durations.
//
// Feature: autosso-refactoring, Property 14: 优雅关闭
// **Validates: Requirements 6.7**
func TestProperty14_GracefulShutdown_VariableWorkDurations(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate number of goroutines (2-10)
		numGoroutines := rapid.IntRange(2, 10).Draw(rt, "numGoroutines")
		// Generate work durations for each goroutine
		workDurations := make([]int, numGoroutines)
		for i := 0; i < numGoroutines; i++ {
			workDurations[i] = rapid.IntRange(1, 100).Draw(rt, fmt.Sprintf("workDuration_%d", i))
		}

		app := NewTestableApplication(5 * time.Second)
		ctx, cancel := context.WithCancel(context.Background())

		var completedCount atomic.Int32
		var completionOrder []int
		var mu sync.Mutex

		// Start goroutines with different work durations
		for i := 0; i < numGoroutines; i++ {
			idx := i
			duration := workDurations[i]
			app.startGoroutine(ctx, fmt.Sprintf("worker-%d", i), func(ctx context.Context) error {
				select {
				case <-time.After(time.Duration(duration) * time.Millisecond):
				case <-ctx.Done():
				}
				completedCount.Add(1)
				mu.Lock()
				completionOrder = append(completionOrder, idx)
				mu.Unlock()
				return nil
			})
		}

		// Cancel and shutdown
		cancel()
		err := app.Shutdown()

		// Property: Shutdown should succeed
		if err != nil {
			rt.Fatalf("Shutdown failed: %v", err)
		}

		// Property: All goroutines should complete
		if int(completedCount.Load()) != numGoroutines {
			rt.Fatalf("Not all goroutines completed:\n"+
				"Expected: %d\n"+
				"Got: %d", numGoroutines, completedCount.Load())
		}

		// Property: Completion order should have all goroutines
		mu.Lock()
		defer mu.Unlock()
		if len(completionOrder) != numGoroutines {
			rt.Fatalf("Completion order missing goroutines:\n"+
				"Expected: %d\n"+
				"Got: %d", numGoroutines, len(completionOrder))
		}
	})
}

// TestProperty14_GracefulShutdown_ShutdownIdempotent tests that multiple shutdowns are safe.
//
// Feature: autosso-refactoring, Property 14: 优雅关闭
// **Validates: Requirements 6.7**
func TestProperty14_GracefulShutdown_ShutdownIdempotent(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate number of shutdown calls (2-5)
		numCalls := rapid.IntRange(2, 5).Draw(rt, "numCalls")

		app := NewTestableApplication(5 * time.Second)

		// Perform multiple sequential shutdowns
		var errors []error
		for i := 0; i < numCalls; i++ {
			err := app.Shutdown()
			errors = append(errors, err)
		}

		// Property: First shutdown should succeed
		if errors[0] != nil {
			rt.Fatalf("First shutdown failed: %v", errors[0])
		}

		// Property: Resources should be closed
		if !app.resourcesClosed.Load() {
			rt.Fatal("Resources were not closed after shutdown")
		}
	})
}

// TestProperty14_GracefulShutdown_GoroutineErrorsDoNotBlockShutdown tests that goroutine errors don't block shutdown.
//
// Feature: autosso-refactoring, Property 14: 优雅关闭
// **Validates: Requirements 6.7**
func TestProperty14_GracefulShutdown_GoroutineErrorsDoNotBlockShutdown(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate number of goroutines (2-10)
		numGoroutines := rapid.IntRange(2, 10).Draw(rt, "numGoroutines")
		// Generate which goroutines will return errors
		errorIndices := make(map[int]bool)
		numErrors := rapid.IntRange(1, numGoroutines).Draw(rt, "numErrors")
		for i := 0; i < numErrors; i++ {
			idx := rapid.IntRange(0, numGoroutines-1).Draw(rt, fmt.Sprintf("errorIndex_%d", i))
			errorIndices[idx] = true
		}

		app := NewTestableApplication(5 * time.Second)
		ctx, cancel := context.WithCancel(context.Background())

		var completedCount atomic.Int32

		// Start goroutines, some of which will return errors
		for i := 0; i < numGoroutines; i++ {
			idx := i
			app.startGoroutine(ctx, fmt.Sprintf("worker-%d", i), func(ctx context.Context) error {
				<-ctx.Done()
				completedCount.Add(1)
				if errorIndices[idx] {
					return fmt.Errorf("error from worker %d", idx)
				}
				return nil
			})
		}

		// Cancel and shutdown
		cancel()
		err := app.Shutdown()

		// Property: Shutdown should succeed despite goroutine errors
		if err != nil {
			rt.Fatalf("Shutdown failed despite goroutine errors: %v", err)
		}

		// Property: All goroutines should complete
		if int(completedCount.Load()) != numGoroutines {
			rt.Fatalf("Not all goroutines completed:\n"+
				"Expected: %d\n"+
				"Got: %d", numGoroutines, completedCount.Load())
		}
	})
}
