package auth

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

func GinGetIdentityInfoInterceptor(authOptions *IdentityAuthOption) func(ctx *gin.Context) {
	return func(c *gin.Context) {
		ctx := c.Request.Context()

		accessOrgId, err := GetAccessOrgIdFromGinContext(c)
		if err != nil {
			tilog.Warn(ctx, "GinGetIdentityInfo GetAccessOrgIdFromGinContext error", zap.Any("path", c.Request.URL.Path), zap.Any("err", err))
		}

		identityTye, identityVal, err := getIdentityTypeValueFromGin(c)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
			c.Abort()
			return
		}

		in := IdentityAuthParam{
			IdentityType:  identityTye,
			IdentityValue: identityVal,
		}
		if authOptions == nil {
			in.Options = IdentityAuthOption{}
		} else {
			in.Options = *authOptions
		}
		in.Options.AccessOrgID = accessOrgId

		ret, err := AuthnIdentity(ctx, &in)
		if err != nil {
			tilog.Warn(ctx, "GinGetIdentityInfo AuthnIdentity error", zap.Any("in", in), zap.Any("err", err))
			c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
			c.Abort()
			return
		}
		if ret.AuthRet != AuthnRetOK {
			tilog.Warn(ctx, "GinGetIdentityInfo AuthnIdentity not pass", zap.Any("in", in), zap.Any("ret", ret))
			c.JSON(http.StatusUnauthorized, gin.H{"error": ret.AuthMessage})
			c.Abort()
			return
		}

		c.Set(IdentityInfoKey, ret.IdentityInfo)
		c.Next()
	}
}

func getIdentityTypeValueFromGin(c *gin.Context) (AuthnIdentityType, string, error) {
	authorizationHeader := c.Request.Header.Get(AuthorizationHeader)
	if authorizationHeader == "" {
		authorizationHeader = c.Request.Header.Get(strings.ToLower(AuthorizationHeader))
	}
	if authorizationHeader != "" {
		identityType := AuthnIdentityTypeUserToken
		identityVal, err := parseIdentityValue(identityType, authorizationHeader)
		return identityType, identityVal, err
	}

	authMethodHeader := c.Request.Header.Get(XAuthMethodHeader)
	if authMethodHeader == XAuthMethodValBear {
		authRawHeader := c.Request.Header.Get(XAuthRawHeader)
		if authRawHeader == "" {
			return "", "", errors.New(fmt.Sprintf("header %s is %s, but header %s is empty", XAuthRawHeader, authMethodHeader, XAuthRawHeader))
		}
		identityType := AuthnIdentityTypeAccessToken
		identityVal, err := parseIdentityValue(identityType, authRawHeader)
		return identityType, identityVal, err
	}
	if authMethodHeader == XAuthMethodValBasic || authMethodHeader == XAuthMethodValDigest {
		authContentHeader := c.Request.Header.Get(XAuthContentHeader)
		if authContentHeader == "" {
			return "", "", errors.New(fmt.Sprintf("header %s is %s, but header %s is empty", XAuthRawHeader, authMethodHeader, XAuthContentHeader))

		}
		identityType := AuthnIdentityTypeApiKeyAK
		identityVal, err := parseIdentityValue(identityType, authContentHeader)
		return identityType, identityVal, err
	}
	return "", "", errors.New(fmt.Sprintf("unkown header %s:%s", XAuthMethodHeader, authMethodHeader))
}

func parseIdentityValue(tye AuthnIdentityType, header string) (string, error) {
	if tye == AuthnIdentityTypeAccessToken {
		headerParts := strings.Split(header, " ")
		if len(headerParts) != 2 {
			return "", fmt.Errorf("access token header invalid: %s", header)
		}
		part0 := strings.ToLower(headerParts[0])
		if part0 == "bearer" {
			return headerParts[1], nil
		} else {
			return "", errors.New(fmt.Sprintf("access token header not starts with bearer, header: %s", header))
		}
	}

	if tye == AuthnIdentityTypeApiKeyAK {
		apiKey := new(OpenApiKeyHeaderInfo)
		err := json.Unmarshal([]byte(header), apiKey)
		if err != nil {
			return "", fmt.Errorf("failed to unmarshal auth content: %s, error: %s", header, err)
		}
		if apiKey.PublicKey == "" {
			return "", fmt.Errorf("get ak from auth content is empty, auth content: %s", header)
		}
		return apiKey.PublicKey, nil
	}

	if tye == AuthnIdentityTypeUserToken {
		headerParts := strings.Split(header, " ")
		if len(headerParts) != 2 {
			return "", fmt.Errorf("authorization header invalid: %s", header)
		}
		part0 := strings.ToLower(headerParts[0])
		if part0 == "bearer" {
			return headerParts[1], nil
		} else {
			return "", errors.New(fmt.Sprintf("user token header not starts with bearer, header: %s", header))
		}

	}

	return "", errors.New(fmt.Sprintf("identity type %s invalid", tye))

}

type OpenApiKeyHeaderInfo struct {
	PublicKey string   `json:"public_key"`
	CIDRs     []string `json:"cidrs,omitempty"`
}
