/*
Package utils provides common utility functions used across all modules.
This file contains property-based tests for panic recovery utilities.

Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
*对于任意* goroutine 中的 panic，应被 recover 捕获，不应导致整个程序崩溃。

**Validates: Requirements 6.6**
*/
package utils

import (
	"errors"
	"fmt"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"pgregory.net/rapid"
)

// TestProperty13_PanicRecovery_StringPanic tests that string panic values are recovered correctly.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_StringPanic(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate an arbitrary string panic value
		panicValue := rapid.String().Draw(rt, "panicValue")

		var capturedInfo *PanicInfo
		var handlerCalled bool

		// Execute function that panics with the string value
		func() {
			defer RecoverWithHandler("test-goroutine", func(info *PanicInfo) {
				handlerCalled = true
				capturedInfo = info
			})
			panic(panicValue)
		}()

		// Property: Handler must be called
		if !handlerCalled {
			rt.Fatalf("Handler was not called for string panic:\n"+
				"Panic value: %q", panicValue)
		}

		// Property: Panic info must not be nil
		if capturedInfo == nil {
			rt.Fatalf("Panic info was nil for string panic:\n"+
				"Panic value: %q", panicValue)
		}

		// Property: Captured value must match the panic value
		capturedStr, ok := capturedInfo.Value.(string)
		if !ok {
			rt.Fatalf("Captured value is not a string:\n"+
				"Expected type: string\n"+
				"Actual type: %T\n"+
				"Value: %v", capturedInfo.Value, capturedInfo.Value)
		}
		if capturedStr != panicValue {
			rt.Fatalf("Captured value does not match panic value:\n"+
				"Expected: %q\n"+
				"Got: %q", panicValue, capturedStr)
		}

		// Property: Goroutine name must be captured
		if capturedInfo.GoroutineName != "test-goroutine" {
			rt.Fatalf("Goroutine name not captured correctly:\n"+
				"Expected: test-goroutine\n"+
				"Got: %s", capturedInfo.GoroutineName)
		}

		// Property: Stack trace must be captured
		if capturedInfo.Stack == "" {
			rt.Fatalf("Stack trace was empty for string panic:\n"+
				"Panic value: %q", panicValue)
		}
	})
}

// TestProperty13_PanicRecovery_IntPanic tests that integer panic values are recovered correctly.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_IntPanic(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate an arbitrary integer panic value
		panicValue := rapid.Int().Draw(rt, "panicValue")

		var capturedInfo *PanicInfo
		var handlerCalled bool

		// Execute function that panics with the integer value
		func() {
			defer RecoverWithHandler("int-panic-test", func(info *PanicInfo) {
				handlerCalled = true
				capturedInfo = info
			})
			panic(panicValue)
		}()

		// Property: Handler must be called
		if !handlerCalled {
			rt.Fatalf("Handler was not called for int panic:\n"+
				"Panic value: %d", panicValue)
		}

		// Property: Panic info must not be nil
		if capturedInfo == nil {
			rt.Fatalf("Panic info was nil for int panic:\n"+
				"Panic value: %d", panicValue)
		}

		// Property: Captured value must match the panic value
		capturedInt, ok := capturedInfo.Value.(int)
		if !ok {
			rt.Fatalf("Captured value is not an int:\n"+
				"Expected type: int\n"+
				"Actual type: %T\n"+
				"Value: %v", capturedInfo.Value, capturedInfo.Value)
		}
		if capturedInt != panicValue {
			rt.Fatalf("Captured value does not match panic value:\n"+
				"Expected: %d\n"+
				"Got: %d", panicValue, capturedInt)
		}

		// Property: Stack trace must be captured
		if capturedInfo.Stack == "" {
			rt.Fatalf("Stack trace was empty for int panic:\n"+
				"Panic value: %d", panicValue)
		}
	})
}

// TestProperty13_PanicRecovery_ErrorPanic tests that error panic values are recovered correctly.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_ErrorPanic(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate an arbitrary error message
		errorMsg := rapid.String().Draw(rt, "errorMsg")
		panicValue := errors.New(errorMsg)

		var capturedInfo *PanicInfo
		var handlerCalled bool

		// Execute function that panics with the error value
		func() {
			defer RecoverWithHandler("error-panic-test", func(info *PanicInfo) {
				handlerCalled = true
				capturedInfo = info
			})
			panic(panicValue)
		}()

		// Property: Handler must be called
		if !handlerCalled {
			rt.Fatalf("Handler was not called for error panic:\n"+
				"Error message: %q", errorMsg)
		}

		// Property: Panic info must not be nil
		if capturedInfo == nil {
			rt.Fatalf("Panic info was nil for error panic:\n"+
				"Error message: %q", errorMsg)
		}

		// Property: Captured value must be an error with the same message
		capturedErr, ok := capturedInfo.Value.(error)
		if !ok {
			rt.Fatalf("Captured value is not an error:\n"+
				"Expected type: error\n"+
				"Actual type: %T\n"+
				"Value: %v", capturedInfo.Value, capturedInfo.Value)
		}
		if capturedErr.Error() != errorMsg {
			rt.Fatalf("Captured error message does not match:\n"+
				"Expected: %q\n"+
				"Got: %q", errorMsg, capturedErr.Error())
		}

		// Property: Stack trace must be captured
		if capturedInfo.Stack == "" {
			rt.Fatalf("Stack trace was empty for error panic:\n"+
				"Error message: %q", errorMsg)
		}
	})
}

// testPanicStruct is a custom struct type for testing struct panic values.
type testPanicStruct struct {
	ID      int
	Message string
	Data    []byte
}

// TestProperty13_PanicRecovery_StructPanic tests that struct panic values are recovered correctly.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_StructPanic(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate an arbitrary struct panic value
		panicValue := testPanicStruct{
			ID:      rapid.Int().Draw(rt, "id"),
			Message: rapid.String().Draw(rt, "message"),
			Data:    rapid.SliceOf(rapid.Byte()).Draw(rt, "data"),
		}

		var capturedInfo *PanicInfo
		var handlerCalled bool

		// Execute function that panics with the struct value
		func() {
			defer RecoverWithHandler("struct-panic-test", func(info *PanicInfo) {
				handlerCalled = true
				capturedInfo = info
			})
			panic(panicValue)
		}()

		// Property: Handler must be called
		if !handlerCalled {
			rt.Fatalf("Handler was not called for struct panic:\n"+
				"Panic value: %+v", panicValue)
		}

		// Property: Panic info must not be nil
		if capturedInfo == nil {
			rt.Fatalf("Panic info was nil for struct panic:\n"+
				"Panic value: %+v", panicValue)
		}

		// Property: Captured value must match the panic value
		capturedStruct, ok := capturedInfo.Value.(testPanicStruct)
		if !ok {
			rt.Fatalf("Captured value is not a testPanicStruct:\n"+
				"Expected type: testPanicStruct\n"+
				"Actual type: %T\n"+
				"Value: %v", capturedInfo.Value, capturedInfo.Value)
		}
		if capturedStruct.ID != panicValue.ID || capturedStruct.Message != panicValue.Message {
			rt.Fatalf("Captured struct does not match panic value:\n"+
				"Expected: %+v\n"+
				"Got: %+v", panicValue, capturedStruct)
		}

		// Property: Stack trace must be captured
		if capturedInfo.Stack == "" {
			rt.Fatalf("Stack trace was empty for struct panic:\n"+
				"Panic value: %+v", panicValue)
		}
	})
}

// TestProperty13_PanicRecovery_ProgramContinues tests that the program continues running after panic recovery.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_ProgramContinues(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate an arbitrary panic value
		panicValue := rapid.String().Draw(rt, "panicValue")

		var executedAfterPanic bool
		var handlerCalled bool

		// Execute function that panics
		func() {
			defer RecoverWithHandler("continue-test", func(info *PanicInfo) {
				handlerCalled = true
			})
			panic(panicValue)
		}()

		// This code should execute after the panic is recovered
		executedAfterPanic = true

		// Property: Code after panic recovery must execute
		if !executedAfterPanic {
			rt.Fatalf("Code after panic recovery did not execute:\n"+
				"Panic value: %q", panicValue)
		}

		// Property: Handler must have been called
		if !handlerCalled {
			rt.Fatalf("Handler was not called:\n"+
				"Panic value: %q", panicValue)
		}
	})
}

// TestProperty13_PanicRecovery_StackTraceContainsGoroutineInfo tests that stack trace contains goroutine information.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_StackTraceContainsGoroutineInfo(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate an arbitrary panic value
		panicValue := rapid.String().Draw(rt, "panicValue")

		var capturedInfo *PanicInfo

		// Execute function that panics
		func() {
			defer RecoverWithHandler("stack-trace-test", func(info *PanicInfo) {
				capturedInfo = info
			})
			panic(panicValue)
		}()

		// Property: Stack trace must contain "goroutine" header
		if !strings.Contains(capturedInfo.Stack, "goroutine") {
			rt.Fatalf("Stack trace does not contain goroutine header:\n"+
				"Stack: %s", capturedInfo.Stack)
		}
	})
}

// TestProperty13_PanicRecovery_SafeGoRecovery tests that SafeGo recovers panics in goroutines.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_SafeGoRecovery(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate an arbitrary panic value
		panicValue := rapid.String().Draw(rt, "panicValue")
		goroutineName := rapid.StringMatching(`[a-z][a-z0-9-]*`).Draw(rt, "goroutineName")

		var capturedInfo *PanicInfo
		var mu sync.Mutex
		done := make(chan struct{})

		// Start a goroutine that will panic
		SafeGo(goroutineName, func() {
			panic(panicValue)
		}, func(info *PanicInfo) {
			mu.Lock()
			capturedInfo = info
			mu.Unlock()
			close(done)
		})

		// Wait for the panic to be handled
		select {
		case <-done:
			// OK
		case <-time.After(2 * time.Second):
			rt.Fatalf("Timeout waiting for panic handler:\n"+
				"Panic value: %q\n"+
				"Goroutine name: %s", panicValue, goroutineName)
		}

		mu.Lock()
		defer mu.Unlock()

		// Property: Panic info must not be nil
		if capturedInfo == nil {
			rt.Fatalf("Panic info was nil in SafeGo:\n"+
				"Panic value: %q", panicValue)
		}

		// Property: Captured value must match
		capturedStr, ok := capturedInfo.Value.(string)
		if !ok || capturedStr != panicValue {
			rt.Fatalf("Captured value does not match in SafeGo:\n"+
				"Expected: %q\n"+
				"Got: %v", panicValue, capturedInfo.Value)
		}

		// Property: Goroutine name must be captured
		if capturedInfo.GoroutineName != goroutineName {
			rt.Fatalf("Goroutine name not captured correctly in SafeGo:\n"+
				"Expected: %s\n"+
				"Got: %s", goroutineName, capturedInfo.GoroutineName)
		}
	})
}

// TestProperty13_PanicRecovery_MustNotPanicReturnsError tests that MustNotPanic returns an error on panic.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_MustNotPanicReturnsError(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate an arbitrary panic value
		panicValue := rapid.String().Draw(rt, "panicValue")

		// Execute function that panics using MustNotPanic
		err := MustNotPanic(func() {
			panic(panicValue)
		})

		// Property: Error must not be nil
		if err == nil {
			rt.Fatalf("MustNotPanic did not return error for panic:\n"+
				"Panic value: %q", panicValue)
		}

		// Property: Error message must contain the panic value
		if !strings.Contains(err.Error(), panicValue) {
			rt.Fatalf("Error message does not contain panic value:\n"+
				"Panic value: %q\n"+
				"Error: %v", panicValue, err)
		}

		// Property: Error message must contain "panic recovered"
		if !strings.Contains(err.Error(), "panic recovered") {
			rt.Fatalf("Error message does not contain 'panic recovered':\n"+
				"Error: %v", err)
		}
	})
}

// TestProperty13_PanicRecovery_MustNotPanicNoPanic tests that MustNotPanic returns nil when no panic.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_MustNotPanicNoPanic(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate an arbitrary value to return (not panic)
		value := rapid.Int().Draw(rt, "value")
		var result int

		// Execute function that does not panic
		err := MustNotPanic(func() {
			result = value * 2
		})

		// Property: Error must be nil when no panic
		if err != nil {
			rt.Fatalf("MustNotPanic returned error when no panic:\n"+
				"Error: %v", err)
		}

		// Property: Function must have executed correctly
		if result != value*2 {
			rt.Fatalf("Function did not execute correctly:\n"+
				"Expected result: %d\n"+
				"Got: %d", value*2, result)
		}
	})
}

// TestProperty13_PanicRecovery_WrapPanicReturnsError tests that WrapPanic returns an error on panic.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_WrapPanicReturnsError(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate an arbitrary panic value
		panicValue := rapid.String().Draw(rt, "panicValue")

		// Execute function that panics using WrapPanic
		err := WrapPanic(func() error {
			panic(panicValue)
		})

		// Property: Error must not be nil
		if err == nil {
			rt.Fatalf("WrapPanic did not return error for panic:\n"+
				"Panic value: %q", panicValue)
		}

		// Property: Error message must contain the panic value
		if !strings.Contains(err.Error(), panicValue) {
			rt.Fatalf("Error message does not contain panic value:\n"+
				"Panic value: %q\n"+
				"Error: %v", panicValue, err)
		}
	})
}

// TestProperty13_PanicRecovery_WrapPanicPreservesError tests that WrapPanic preserves the original error.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_WrapPanicPreservesError(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate an arbitrary error message
		errorMsg := rapid.String().Draw(rt, "errorMsg")

		// Execute function that returns an error (no panic)
		err := WrapPanic(func() error {
			return fmt.Errorf("%s", errorMsg)
		})

		// Property: Error must not be nil
		if err == nil {
			rt.Fatalf("WrapPanic did not return error:\n"+
				"Error message: %q", errorMsg)
		}

		// Property: Error message must match the original error
		if err.Error() != errorMsg {
			rt.Fatalf("Error message does not match original:\n"+
				"Expected: %q\n"+
				"Got: %q", errorMsg, err.Error())
		}
	})
}

// TestProperty13_PanicRecovery_ConcurrentPanics tests that concurrent panics are all recovered.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_ConcurrentPanics(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate number of concurrent goroutines (between 2 and 20)
		numGoroutines := rapid.IntRange(2, 20).Draw(rt, "numGoroutines")

		var recoveredCount int64
		var wg sync.WaitGroup

		for i := 0; i < numGoroutines; i++ {
			wg.Add(1)
			go func(id int) {
				defer wg.Done()
				defer RecoverWithHandler(fmt.Sprintf("worker-%d", id), func(info *PanicInfo) {
					atomic.AddInt64(&recoveredCount, 1)
				})
				panic(fmt.Sprintf("panic from worker %d", id))
			}(i)
		}

		wg.Wait()

		// Property: All panics must be recovered
		if int(recoveredCount) != numGoroutines {
			rt.Fatalf("Not all panics were recovered:\n"+
				"Expected: %d\n"+
				"Got: %d", numGoroutines, recoveredCount)
		}
	})
}

// TestProperty13_PanicRecovery_NilHandler tests that nil handler doesn't cause additional panic.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_NilHandler(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate an arbitrary panic value
		panicValue := rapid.String().Draw(rt, "panicValue")

		var executedAfterPanic bool

		// Execute function that panics with nil handler
		func() {
			defer RecoverWithHandler("nil-handler-test", nil)
			panic(panicValue)
		}()

		// This code should execute after the panic is recovered
		executedAfterPanic = true

		// Property: Code after panic recovery must execute even with nil handler
		if !executedAfterPanic {
			rt.Fatalf("Code after panic recovery did not execute with nil handler:\n"+
				"Panic value: %q", panicValue)
		}
	})
}

// TestProperty13_PanicRecovery_PanicInfoString tests that PanicInfo.String() produces valid output.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_PanicInfoString(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate arbitrary panic info components
		panicValue := rapid.String().Draw(rt, "panicValue")
		goroutineName := rapid.String().Draw(rt, "goroutineName")
		stack := rapid.String().Draw(rt, "stack")

		info := &PanicInfo{
			Value:         panicValue,
			GoroutineName: goroutineName,
			Stack:         stack,
		}

		str := info.String()

		// Property: String must contain the panic value
		if !strings.Contains(str, fmt.Sprintf("%v", panicValue)) {
			rt.Fatalf("PanicInfo.String() does not contain panic value:\n"+
				"Panic value: %q\n"+
				"String: %s", panicValue, str)
		}

		// Property: String must contain goroutine name if not empty
		if goroutineName != "" && !strings.Contains(str, goroutineName) {
			rt.Fatalf("PanicInfo.String() does not contain goroutine name:\n"+
				"Goroutine name: %q\n"+
				"String: %s", goroutineName, str)
		}

		// Property: String must contain stack if not empty
		if stack != "" && !strings.Contains(str, stack) {
			rt.Fatalf("PanicInfo.String() does not contain stack:\n"+
				"Stack: %q\n"+
				"String: %s", stack, str)
		}
	})
}

// TestProperty13_PanicRecovery_NilPanicValue tests that nil panic values are handled correctly.
// Note: In Go 1.21+, panic(nil) is converted to a *runtime.PanicNilError with message
// "panic called with nil argument". This test verifies the panic is still recovered.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_NilPanicValue(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		var capturedInfo *PanicInfo
		var handlerCalled bool

		// Execute function that panics with nil
		func() {
			defer RecoverWithHandler("nil-panic-test", func(info *PanicInfo) {
				handlerCalled = true
				capturedInfo = info
			})
			panic(nil)
		}()

		// Property: Handler must be called even for nil panic
		if !handlerCalled {
			rt.Fatal("Handler was not called for nil panic")
		}

		// Property: Panic info must not be nil
		if capturedInfo == nil {
			rt.Fatal("Panic info was nil for nil panic")
		}

		// Property: Panic must be recovered (value can be nil or runtime.PanicNilError in Go 1.21+)
		// In Go 1.21+, panic(nil) is converted to *runtime.PanicNilError
		// We just verify the panic was recovered and program continues
		// The captured value will be either nil (Go < 1.21) or *runtime.PanicNilError (Go >= 1.21)

		// Property: Stack trace must be captured
		if capturedInfo.Stack == "" {
			rt.Fatal("Stack trace was empty for nil panic")
		}
	})
}

// TestProperty13_PanicRecovery_SafeGoNoPanic tests that SafeGo works correctly when no panic occurs.
//
// Feature: autosso-refactoring, Property 13: Goroutine Panic 恢复
// **Validates: Requirements 6.6**
func TestProperty13_PanicRecovery_SafeGoNoPanic(t *testing.T) {
	rapid.Check(t, func(rt *rapid.T) {
		// Generate an arbitrary value to compute
		value := rapid.IntRange(1, 1000).Draw(rt, "value")

		var result int64
		var handlerCalled int64
		var wg sync.WaitGroup

		wg.Add(1)
		SafeGo("no-panic-test", func() {
			defer wg.Done()
			atomic.StoreInt64(&result, int64(value*2))
		}, func(info *PanicInfo) {
			atomic.StoreInt64(&handlerCalled, 1)
		})

		// Wait for goroutine to complete
		done := make(chan struct{})
		go func() {
			wg.Wait()
			close(done)
		}()

		select {
		case <-done:
			// OK
		case <-time.After(2 * time.Second):
			rt.Fatalf("Timeout waiting for goroutine to complete:\n"+
				"Value: %d", value)
		}

		// Property: Handler must NOT be called when no panic
		if atomic.LoadInt64(&handlerCalled) != 0 {
			rt.Fatalf("Handler was called when no panic occurred:\n"+
				"Value: %d", value)
		}

		// Property: Function must have executed correctly
		if atomic.LoadInt64(&result) != int64(value*2) {
			rt.Fatalf("Function did not execute correctly:\n"+
				"Expected result: %d\n"+
				"Got: %d", value*2, atomic.LoadInt64(&result))
		}
	})
}
