package api

import (
	"context"

	"github.com/tidbcloud/aipower/commons/config"
	"github.com/tidbcloud/aipower/commons/ginparser"
	"github.com/tidbcloud/aipower/commons/grpc"
	"github.com/tidbcloud/aipower/commons/metrics"

	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus"
	errors "github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/transport/http/tigin"

	"github.com/tidbcloud/account/idl/pbgen/proto/account"
)

const (
	MAX = 1024 * 1024
)

// Metric metric middleware
func Metric() gin.HandlerFunc {
	return func(c *gin.Context) {
		tBegin := time.Now()
		c.Next()

		duration := float64(time.Since(tBegin)) / float64(time.Millisecond)

		//path := parsePath(c.Request.URL.Path)
		path := c.FullPath()

		metrics.HTTPReqTotal.With(prometheus.Labels{
			"method": c.Request.Method,
			"path":   path,
			"status": strconv.Itoa(c.Writer.Status()),
		}).Inc()

		metrics.HTTPReqDuration.With(prometheus.Labels{
			"method": c.Request.Method,
			"path":   path,
		}).Observe(duration)
	}
}

func CheckAuth(c *gin.Context) (*account.User, error) {
	ctx := c.Request.Context()
	user, bizErr := getUser(c, ctx)
	if bizErr != nil {
		return nil, errors.NewUnauthorized("GetUser Failed. "+bizErr.Error(), bizErr)
	}
	return user, nil
}

func CheckPermission(c *gin.Context) error {
	ctx := c.Request.Context()
	params := ginparser.NewCommonParamsByGin(c)
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID}
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		return validateErr
	}

	listOrgsByUserReq := &account.ListOrgsByUserRequest{UserId: params.User.Id}
	orgFlag := false
	orgs, err := grpc.ListOrgsByUser(ctx, listOrgsByUserReq)
	if err != nil {
		return errors.NewInternalServerError("ListOrgsByUser failed.", err)
	}
	for _, v := range orgs.Orgs {
		if v.Id == params.OrgID {
			orgFlag = true
			break
		}
	}
	if !orgFlag {
		return errors.NewForbidden("check org failed. ", nil)
	}

	projectFlag := false
	listProjectsByOrgsInUserRequest := &account.ListProjectsByOrgsInUserRequest{
		UserId: params.User.Id,
		OrgIds: []uint64{params.OrgID},
	}
	projects, bizErr := grpc.ListProjectsByOrgsInUser(ctx, listProjectsByOrgsInUserRequest)
	if bizErr != nil {
		return errors.NewInternalServerError("ListProjectsByOrgsInUser failed.", bizErr)
	}
	for _, v1 := range projects.Projects {
		for _, v2 := range v1.Projects {
			if v2.Id == params.ProjectID {
				projectFlag = true
				break
			}
		}
	}
	if !projectFlag {
		return errors.NewForbidden("check project failed. ", nil)
	}
	return nil
}

func CheckUser(c *gin.Context) {
	if IsTestRequest(c) {
		return
	}
	authUser, err := CheckAuth(c)
	if err != nil {
		tigin.AbortWithError(c, err)
		return
	}
	c.Set(ginparser.CONTEXT_ACCOUNT_USER, authUser)
	//err = CheckPermission(c)
	//if err != nil {
	//	tigin.AbortWithError(c, err)
	//	return
	//}
	c.Next()
}

func CheckRequestBodySize(c *gin.Context) {
	x := c.Request.ContentLength
	if x > MAX {
		tigin.AbortWithError(c, errors.NewBadRequest("request body is too large", nil))
		return
	}
	c.Next()
}

func getUser(c *gin.Context, ctx context.Context) (*account.User, error) {
	rawToken, err := GetToken(c)
	if err != nil {
		return nil, err
	}
	req := &account.GetUserByTokenRequest{
		Token: rawToken,
	}
	resp, bizErr := grpc.GetUserByToken(ctx, req)
	if bizErr != nil {
		return nil, bizErr
	}
	return resp.User, nil
}

func GetToken(c *gin.Context) (string, error) {
	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 "", errors.NewUnauthorized("token not found", nil)
	}

	tokenSlice := strings.Split(header, " ")
	if len(tokenSlice) != 2 || strings.ToLower(tokenSlice[0]) != "bearer" {
		return "", errors.NewUnauthorized("token invalid", nil)
	}

	return tokenSlice[1], nil
}

func IsTestRequest(c *gin.Context) bool {
	if !config.IsLocalMode() {
		return false
	}
	header := c.GetHeader("test-email")
	if header != "" {
		currentUser := &account.User{Email: header}
		c.Set(ginparser.CONTEXT_ACCOUNT_USER, currentUser)
		return true
	}
	return false
}
