// Package crashlog 提供详细的崩溃日志、panic 恢复和 goroutine 保护工具。
// 在开发阶段用于快速定位崩溃点。
package crashlog

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"runtime"
	"runtime/debug"
	"time"
)

// Go 在单独的 goroutine 中运行 fn，自动捕获 panic 并记录完整堆栈。
// 返回一个 channel，在 goroutine 退出时关闭。
// 用法: go crashlog.Go("thinker-light", func() { ... })
func Go(name string, fn func()) {
	go func() {
		defer Recover(name)
		fn()
	}()
}

// Recover 用于 defer 语句中，捕获 panic 并记录完整调用栈。
// 用法: defer crashlog.Recover("thinker-deep")
func Recover(name string) {
	if r := recover(); r != nil {
		stack := debug.Stack()
		log.Printf("[CRASH] goroutine=%s panic=%v\n%s", name, r, string(stack))
		// 写独立崩溃日志文件，方便事后排查
		writeCrashFile(name, fmt.Sprintf("panic: %v\n\n%s", r, string(stack)))
	}
}

// RecoverNoop is a no-op for production hot paths.
func RecoverNoop(name string) {}

// WrapHTTP 返回一个 HTTP 中间件，自动捕获 handler 中的 panic。
// 用法: http.Handle("/api", crashlog.WrapHTTP(handler))
func WrapHTTP(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if rec := recover(); rec != nil {
				stack := debug.Stack()
				log.Printf("[CRASH] http-panic url=%s method=%s panic=%v\n%s",
					r.URL.String(), r.Method, rec, string(stack))
				writeCrashFile("http-"+sanitize(r.URL.String()), fmt.Sprintf("panic: %v\n\n%s", rec, string(stack)))
				http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			}
		}()
		next.ServeHTTP(w, r)
	})
}

// WrapHTTPFunc 返回一个 HTTP handler 函数中间件。
func WrapHTTPFunc(fn http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if rec := recover(); rec != nil {
				stack := debug.Stack()
				log.Printf("[CRASH] http-panic url=%s method=%s panic=%v\n%s",
					r.URL.String(), r.Method, rec, string(stack))
				writeCrashFile("http-"+sanitize(r.URL.String()), fmt.Sprintf("panic: %v\n\n%s", rec, string(stack)))
				http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			}
		}()
		fn(w, r)
	}
}

// LLMCall 记录 LLM API 调用的开始时间，返回一个结束函数。
// 用法:
//
//	defer crashlog.LLMCall("deep-think", model)(&err, &responseLen)
func LLMCall(caller, model string) func(err *error, responseBytes *int) {
	start := time.Now()
	return func(err *error, responseBytes *int) {
		elapsed := time.Since(start)
		level := "OK"
		errMsg := ""
		if err != nil && *err != nil {
			level = "FAIL"
			errMsg = (*err).Error()
		}
		respLen := 0
		if responseBytes != nil {
			respLen = *responseBytes
		}
		if elapsed > 10*time.Second {
			level = "SLOW(" + level + ")"
		}
		log.Printf("[LLM] caller=%s model=%s elapsed=%v result=%s resp_len=%d err=%s",
			caller, model, elapsed.Round(time.Millisecond), level, respLen, errMsg)
	}
}

// ── internal helpers ──

func writeCrashFile(name string, content string) {
	// 写到 logs/ 目录，持久化保留
	os.MkdirAll("logs", 0755)
	timestamp := time.Now().Format("20060102_150405")
	filename := fmt.Sprintf("logs/crash_%s_%s.log", sanitize(name), timestamp)
	f, err := os.Create(filename)
	if err != nil {
		log.Printf("[CRASH] 无法写入崩溃日志文件 %s: %v", filename, err)
		return
	}
	defer f.Close()
	fmt.Fprintf(f, "=== CRASH REPORT ===\n")
	fmt.Fprintf(f, "Time: %s\n", time.Now().Format(time.RFC3339))
	fmt.Fprintf(f, "Goroutine: %s\n", name)
	fmt.Fprintf(f, "Go Version: %s\n", runtime.Version())
	fmt.Fprintf(f, "GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0))
	fmt.Fprintf(f, "NumGoroutine: %d\n", runtime.NumGoroutine())
	fmt.Fprintf(f, "\n%s", content)
	log.Printf("[CRASH] 崩溃日志已写入 %s", filename)
}

func sanitize(s string) string {
	result := make([]byte, 0, len(s))
	for i := 0; i < len(s) && i < 100; i++ {
		c := s[i]
		if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' {
			result = append(result, c)
		} else {
			result = append(result, '_')
		}
	}
	return string(result)
}
