package interceptor

import (
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/pingcap/log"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/structs"
	"go.uber.org/zap"
)

func Log(c *gin.Context) {
	logger := log.L().Named("[GIN-AccessLog]")
	if c == nil || c.Writer == nil || c.Request == nil {
		return
	}

	// Record start time
	startTime := time.Now()

	var requestID string

	if reqHeaderRequestId := c.Request.Header.Get(string(constants.OpenAPIHeaderRequestId)); len(reqHeaderRequestId) > 0 {
		requestID = reqHeaderRequestId
	} else {
		requestID = uuid.New().String()
	}
	c.Header(string(constants.OpenAPIHeaderRequestId), requestID)
	c.Set(string(constants.OpenAPIHeaderRequestId), requestID)

	logFields := []zap.Field{
		zap.String("method", c.Request.Method),
		zap.String("path", c.Request.URL.Path),
		zap.String("query", c.Request.URL.RawQuery),
		zap.String("request_id", requestID),
		zap.String("source_client_ip", c.Request.Header.Get("X-Forwarded-For")),
	}

	if obj, exists := c.Get(structs.GlobalServerRequestParametersKey); exists {
		reqParams := obj.(structs.RequestParameters)
		logFields = append(logFields,
			zap.String("vendor", string(reqParams.Vendor)),
			zap.String("region", reqParams.Region),
		)
	}

	// Log incoming request
	logger.Info("incoming request", logFields...)

	// request processing
	c.Next()

	duration := time.Since(startTime)

	statusCode := c.Writer.Status()
	logFields = append(logFields,
		zap.Int("status", statusCode),
		zap.Duration("latency", duration),
	)

	switch {
	case statusCode >= 500:
		logFields = withErrorContext(c, logFields)
		logger.Error("request completed", logFields...)
	case statusCode >= 400:
		logFields = withErrorContext(c, logFields)
		logger.Info("request completed", logFields...)
	case statusCode >= 200 && statusCode < 300:
		logger.Debug("request completed", logFields...)
	default:
		logger.Debug("request completed", logFields...)
	}
}

func withErrorContext(c *gin.Context, logFields []zap.Field) []zap.Field {
	if c == nil || c.Request == nil {
		return logFields
	}
	if len(c.Errors) > 0 {
		logFields = append(logFields, zap.String("errors", c.Errors.String()))
	}

	if c.Request.Context() != nil {
		if requestResult := c.Request.Context().Value(constants.RequestResultCtxKey); requestResult != nil {
			logFields = append(logFields, zap.Any("request_result", requestResult))
		}
	}

	var body string
	if cb, ok := c.Get(gin.BodyBytesKey); ok {
		if cbb, ok := cb.([]byte); ok {
			body = string(cbb)
			logFields = append(logFields, zap.String("body", body))
		}
	}

	return logFields
}
