// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package common

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"strings"

	user_model "gitea.dev/models/user"
	"gitea.dev/modules/httpcache"
	"gitea.dev/modules/log"
	"gitea.dev/modules/reqctx"
	"gitea.dev/modules/setting"
	"gitea.dev/modules/templates"
	"gitea.dev/modules/web/middleware"
	"gitea.dev/modules/web/routing"
	"gitea.dev/services/context"
)

const tplStatus500 templates.TplName = "status/500"

func renderServerErrorPage(w http.ResponseWriter, req *http.Request, respCode int, tmpl templates.TplName, ctxData map[string]any, plainMsg string) {
	acceptsHTML := false
	for _, part := range req.Header["Accept"] {
		if strings.Contains(part, "text/html") {
			acceptsHTML = true
			break
		}
	}

	httpcache.SetCacheControlInHeader(w.Header(), &httpcache.CacheControlOptions{})
	tmplCtx := context.NewTemplateContextForWeb(reqctx.FromContext(req.Context()), req, middleware.Locale(w, req))
	w.WriteHeader(respCode)

	outBuf := &bytes.Buffer{}
	if acceptsHTML {
		err := templates.PageRenderer().HTML(outBuf, respCode, tmpl, ctxData, tmplCtx)
		if err != nil {
			_, _ = w.Write([]byte("Internal server error but failed to render error page template, please collect error logs and report to Gitea issue tracker"))
			return
		}
	} else {
		outBuf.WriteString(plainMsg)
	}
	_, _ = io.Copy(w, outBuf)
}

// renderPanicErrorPage renders a 500 page with the recovered panic value, it handles the stack trace, and it never panics
func renderPanicErrorPage(w http.ResponseWriter, req *http.Request, recovered any) {
	combinedErr := fmt.Errorf("%v\n%s", recovered, log.Stack(2))
	log.Error("PANIC: %v", combinedErr)

	defer func() {
		if err := recover(); err != nil {
			log.Error("Panic occurs again when rendering error page: %v. Stack:\n%s", combinedErr, log.Stack(2))
		}
	}()

	routing.UpdatePanicError(req.Context(), combinedErr)

	plainMsg := "Internal Server Error"
	ctxData := middleware.GetContextData(req.Context())
	// This recovery handler could be called without Gitea's web context, so we shouldn't touch that context too much.
	// Otherwise, the 500-page may cause new panics, eg: cache.GetContextWithData, it makes the developer&users couldn't find the original panic.
	user, _ := ctxData[middleware.ContextDataKeySignedUser].(*user_model.User)
	if !setting.IsProd || (user != nil && user.IsAdmin) {
		plainMsg = "PANIC: " + combinedErr.Error()
		ctxData["ErrorMsg"] = plainMsg
	}
	renderServerErrorPage(w, req, http.StatusInternalServerError, tplStatus500, ctxData, plainMsg)
}
