// Package utils provides utility functions for the AutoSSO application.
// This file contains panic recovery utilities for goroutines.
//
// Requirements: 6.6
package utils

import (
	"fmt"
	"runtime"
	"strings"
)

// PanicInfo contains information about a recovered panic.
type PanicInfo struct {
	// Value is the value passed to panic()
	Value interface{}
	// Stack is the stack trace at the point of panic
	Stack string
	// GoroutineName is the name of the goroutine that panicked (if provided)
	GoroutineName string
}

// String returns a formatted string representation of the panic info.
func (p *PanicInfo) String() string {
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("panic: %v", p.Value))
	if p.GoroutineName != "" {
		sb.WriteString(fmt.Sprintf(" (goroutine: %s)", p.GoroutineName))
	}
	if p.Stack != "" {
		sb.WriteString("\n")
		sb.WriteString(p.Stack)
	}
	return sb.String()
}

// RecoverFunc is a function type for handling recovered panics.
// It receives the panic info and can perform logging or other actions.
type RecoverFunc func(info *PanicInfo)

// SafeGo runs a function in a goroutine with panic recovery.
// If the function panics, the panic is recovered and passed to the handler.
// If handler is nil, the panic is silently recovered.
//
// Parameters:
//   - name: Name of the goroutine for identification in logs
//   - fn: Function to run in the goroutine
//   - handler: Function to call if a panic occurs (can be nil)
//
// Example:
//
//	utils.SafeGo("worker", func() {
//	    // do work that might panic
//	}, func(info *utils.PanicInfo) {
//	    log.Error("Worker panicked", logger.String("panic", info.String()))
//	})
//
// Requirements: 6.6
func SafeGo(name string, fn func(), handler RecoverFunc) {
	go func() {
		defer RecoverWithHandler(name, handler)
		fn()
	}()
}

// RecoverWithHandler recovers from a panic and calls the handler with panic info.
// This should be called with defer at the start of a goroutine.
//
// Parameters:
//   - name: Name of the goroutine for identification
//   - handler: Function to call if a panic occurs (can be nil)
//
// Example:
//
//	go func() {
//	    defer utils.RecoverWithHandler("worker", func(info *utils.PanicInfo) {
//	        log.Error("Worker panicked", logger.String("panic", info.String()))
//	    })
//	    // do work that might panic
//	}()
//
// Requirements: 6.6
func RecoverWithHandler(name string, handler RecoverFunc) {
	if r := recover(); r != nil {
		info := &PanicInfo{
			Value:         r,
			Stack:         GetStackTrace(3), // Skip recover, RecoverWithHandler, and runtime.gopanic
			GoroutineName: name,
		}
		if handler != nil {
			handler(info)
		}
	}
}

// GetStackTrace returns the current stack trace as a string.
// The skip parameter specifies how many stack frames to skip.
//
// Parameters:
//   - skip: Number of stack frames to skip (0 = include GetStackTrace itself)
//
// Returns a formatted stack trace string.
func GetStackTrace(skip int) string {
	const maxStackSize = 4096
	buf := make([]byte, maxStackSize)
	n := runtime.Stack(buf, false)
	stack := string(buf[:n])

	// Skip the specified number of frames
	lines := strings.Split(stack, "\n")
	if len(lines) <= skip*2+1 {
		return stack
	}

	// Keep the goroutine header and skip the specified frames
	// Each frame consists of 2 lines: function name and file:line
	var result strings.Builder
	result.WriteString(lines[0]) // goroutine header
	result.WriteString("\n")

	// Skip frames (each frame is 2 lines)
	startLine := 1 + skip*2
	for i := startLine; i < len(lines); i++ {
		result.WriteString(lines[i])
		if i < len(lines)-1 {
			result.WriteString("\n")
		}
	}

	return result.String()
}

// MustNotPanic wraps a function and ensures it doesn't panic.
// If the function panics, the panic is recovered and the error is returned.
// This is useful for wrapping functions that should not panic in production.
//
// Parameters:
//   - fn: Function to wrap
//
// Returns an error if the function panicked, nil otherwise.
//
// Example:
//
//	err := utils.MustNotPanic(func() {
//	    // do work that might panic
//	})
//	if err != nil {
//	    log.Error("Function panicked", logger.Err(err))
//	}
//
// Requirements: 6.6
func MustNotPanic(fn func()) (err error) {
	defer func() {
		if r := recover(); r != nil {
			stack := GetStackTrace(3)
			err = fmt.Errorf("panic recovered: %v\n%s", r, stack)
		}
	}()
	fn()
	return nil
}

// WrapPanic wraps a function that returns an error and adds panic recovery.
// If the function panics, the panic is converted to an error.
//
// Parameters:
//   - fn: Function to wrap
//
// Returns the error from the function, or a panic error if the function panicked.
//
// Example:
//
//	err := utils.WrapPanic(func() error {
//	    return doSomethingDangerous()
//	})
//
// Requirements: 6.6
func WrapPanic(fn func() error) (err error) {
	defer func() {
		if r := recover(); r != nil {
			stack := GetStackTrace(3)
			err = fmt.Errorf("panic recovered: %v\n%s", r, stack)
		}
	}()
	return fn()
}
