package controller

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

	"github.com/go-logr/logr"
	"github.com/tidbcloud/devops/api"
	ctrlcommon "github.com/tidbcloud/devops/pkg/controller/common"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	metrics "github.com/tidbcloud/devops/pkg/infra/metrics/devops-server"
	"go.opentelemetry.io/otel/trace"

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

func GetAccessTokenFromContext(c *gin.Context) string {
	header := c.GetHeader("authorization")
	if header == "" {
		/*
		   since HTTP/2 use a lower case header, HTTP/1.x header is case-insensitive,
		   but generally we use a Authorization header in HTTP/1.x
		   LINK:
		   https://tools.ietf.org/html/rfc7540#section-8.1.2
		   https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
		*/
		header = c.GetHeader("Authorization")
	}

	if header == "" {
		return ""
	}

	fields := strings.Split(header, " ")
	if len(fields) != 2 || strings.ToLower(fields[0]) != "bearer" {
		return ""
	}

	return fields[1]
}

func GetAPITokenFromContext(c *gin.Context) string {
	return c.GetHeader(consts.HTTPHeaderAPIToken)
}

func GetAPIKeyFromContext(c *gin.Context) string {
	// https://swagger.io/docs/specification/authentication/api-keys/
	return c.GetHeader(consts.HTTPHeaderAPIKey)
}

func NewLogMetricMiddlewareBuilder(requestLogger, accessLogger logr.Logger) *ctrlcommon.LogMetricMiddlewareBuilder {
	builder := &ctrlcommon.LogMetricMiddlewareBuilder{
		Request: ctrlcommon.RequestLoggerOptions{
			Logger: requestLogger,
			FilterFunc: func(c *gin.Context) bool {
				if c.Request.Method == http.MethodOptions {
					return true
				}
				if c.Request.URL.Path == "/metrics" || c.Request.URL.Path == "/healthz" {
					return true
				}
				return false
			},
		},
		Metric: &metrics.Gin,
	}
	if !accessLogger.IsZero() {
		builder.Access = &ctrlcommon.AccessLoggerOptions{
			Logger: accessLogger,
			FilterFunc: func(c *gin.Context) bool {
				return strings.HasPrefix(c.FullPath(), InteralAPIPathPrefix)
			},
			ExtraFieldsFunc: func(c *gin.Context) map[string]any {
				return map[string]any{
					"operator":      consts.GetOperatorFromCtx(c, "unknown"),
					"operator_type": consts.GetOperatorTypeFromCtx(c),
				}
			},
		}
	}
	return builder
}

func FailWithCode(c *gin.Context, err error, code api.ErrCode) {
	resp := &api.BaseResp{
		ErrCode:    code,
		ErrMessage: err.Error(),
		TraceID:    trace.SpanContextFromContext(c).TraceID().String(),
	}
	c.AbortWithStatusJSON(code.HTTPCode(), resp)
	c.Error(err)
}

// Fail aborts the request with an error.
// If the error is an APIError, its code and message will be used.
// Otherwise, the error will be treated as an internal error.
func Fail(c *gin.Context, err error) {
	aerr := errs.APIErrorWrap(err)
	resp := &api.BaseResp{
		ErrCode:    aerr.ErrCode(),
		ErrMessage: aerr.ErrMessage(), // TODO: hide internal error message?
		TraceID:    trace.SpanContextFromContext(c).TraceID().String(),
	}
	c.AbortWithStatusJSON(aerr.ErrCode().HTTPCode(), resp)
	c.Error(err)
}

// Success sets the errcode to 0 and returns the response.
func Success(c *gin.Context, resp any) {
	if resp == nil {
		resp = &api.BaseResp{}
	}
	api.SetBaseResp(resp, api.ErrCodeOK, "", trace.SpanContextFromContext(c).TraceID().String())
	c.JSON(api.ErrCodeOK.HTTPCode(), resp)
}

// FetchBody fetches the request body and puts it back to the request.
func FetchBody(c *gin.Context) ([]byte, error) {
	body, err := c.GetRawData()
	c.Request.Body.Close()
	if err != nil {
		return nil, err
	}

	c.Request.Body = io.NopCloser(bytes.NewReader(body))
	return body, nil
}
