package middleware

import (
	"bytes"
	"errors"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/gin-gonic/gin"
)

func runErrorAudit(t *testing.T, path, response string, logger *slog.Logger) {
	t.Helper()
	gin.SetMode(gin.TestMode)
	engine := gin.New()
	engine.Use(ErrorAudit(logger))
	engine.GET(path, func(c *gin.Context) {
		c.Set(ctxRespBufferKey, bytes.NewBufferString(response))
		c.Set("request_id", "request-1")
		c.Set("trace_id", "trace-1")
		c.Status(http.StatusOK)
	})
	request := httptest.NewRequest(http.MethodGet, path, nil)
	responseRecorder := httptest.NewRecorder()
	engine.ServeHTTP(responseRecorder, request)
}

func TestErrorAuditEmitsUnexpectedBusinessFailure(t *testing.T) {
	var output bytes.Buffer
	logger := slog.New(slog.NewJSONHandler(&output, nil))

	runErrorAudit(t, "/test", `{"code":7,"data":{},"msg":"数据库写入失败"}`, logger)

	if text := output.String(); !strings.Contains(text, `"mod":"biz"`) || !strings.Contains(text, `"error":"数据库写入失败"`) {
		t.Fatalf("unexpected error log: %s", text)
	}
}

func TestErrorAuditUsesFeatureModule(t *testing.T) {
	var output bytes.Buffer
	logger := slog.New(slog.NewJSONHandler(&output, nil))

	runErrorAudit(t, "/timedTask/triggerTimedTask", `{"code":7,"data":{},"msg":"任务执行失败"}`, logger)
	if text := output.String(); !strings.Contains(text, `"mod":"timedTask"`) {
		t.Fatalf("unexpected feature log module: %s", text)
	}
}

func TestErrorAuditSkipsExpectedClientFailure(t *testing.T) {
	var output bytes.Buffer
	logger := slog.New(slog.NewJSONHandler(&output, nil))

	runErrorAudit(t, "/test", `{"code":7,"data":{},"msg":"参数错误"}`, logger)

	if output.Len() != 0 {
		t.Fatalf("expected no error log, got %s", output.String())
	}
}

func TestErrorAuditAllowsNilLogger(t *testing.T) {
	runErrorAudit(t, "/test", `{"code":7,"data":{},"msg":"数据库写入失败"}`, nil)
}

func TestErrorAuditSkipsLogViewerFailureAlreadyLoggedByHandler(t *testing.T) {
	var output bytes.Buffer
	logger := slog.New(slog.NewJSONHandler(&output, nil))

	runErrorAudit(t, "/logViewer/content", `{"code":7,"data":{},"msg":"日志文件不可读取"}`, logger)

	if output.Len() != 0 {
		t.Fatalf("log viewer failure must not be emitted twice, got %s", output.String())
	}
}

func TestErrorAuditEmitsPrivateMiddlewareErrorOnSuccessResponse(t *testing.T) {
	var output bytes.Buffer
	logger := slog.New(slog.NewJSONHandler(&output, nil))
	gin.SetMode(gin.TestMode)
	engine := gin.New()
	engine.Use(ErrorAudit(logger))
	engine.GET("/test", func(c *gin.Context) {
		c.Error(errors.New("operation audit persist: database unavailable"))
		c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{}, "msg": "成功"})
	})
	response := httptest.NewRecorder()
	engine.ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/test", nil))
	if !strings.Contains(output.String(), "operation audit persist: database unavailable") {
		t.Fatalf("private middleware error was not logged: %s", output.String())
	}
}

func TestErrorAuditEmitsOperationAuditFailureForSysErrorRoute(t *testing.T) {
	var output bytes.Buffer
	logger := slog.New(slog.NewJSONHandler(&output, nil))
	gin.SetMode(gin.TestMode)
	engine := gin.New()
	engine.Use(ErrorAudit(logger))
	engine.PUT("/sysError/updateSysError", func(c *gin.Context) {
		c.Set(ctxOperationAuditPersistFailedKey, true)
		c.Error(errors.New("operation audit persist: database unavailable"))
		c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{}, "msg": "成功"})
	})
	response := httptest.NewRecorder()
	engine.ServeHTTP(response, httptest.NewRequest(http.MethodPut, "/sysError/updateSysError", nil))
	if !strings.Contains(output.String(), "operation audit persist: database unavailable") {
		t.Fatalf("sysError operation audit failure was not logged: %s", output.String())
	}
}
