package auth

import (
	"context"
	"errors"
	"fmt"
	"strings"

	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func GrpcGatewayGetIdentityInfoUnaryInterceptor(authOptions *IdentityAuthOption) grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		if !WhetherRequestComeFromGrpcGateway(ctx) {
			return handler(ctx, req)
		}

		identityInfo, err := grpcGatewayAuthnIdentity(ctx, authOptions)
		if err != nil {
			tilog.Warn(ctx, "GrpcGatewayUnaryGetIdentityInfo grpcGateWaryAuthIdentity error", zap.Any("err", err))
			return nil, status.Error(codes.Unauthenticated, err.Error())
		}

		ctx = context.WithValue(ctx, IdentityInfoKey, identityInfo)

		return handler(ctx, req)
	}
}

func GrpcGatewayGetIdentityInfoStreamServerInterceptor(authOptions *IdentityAuthOption) grpc.StreamServerInterceptor {
	return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
		ctx := ss.Context()
		if !WhetherRequestComeFromGrpcGateway(ctx) {
			return handler(srv, ss)
		}
		identityInfo, err := grpcGatewayAuthnIdentity(ctx, authOptions)
		if err != nil {
			tilog.Warn(ctx, "GrpcGatewayStreamGetIdentityInfo grpcGateWaryAuthIdentity error", zap.Any("err", err))
			return status.Error(codes.Unauthenticated, err.Error())
		}

		ctx = context.WithValue(ctx, IdentityInfoKey, identityInfo)

		wrapped := grpc_middleware.WrapServerStream(ss)
		wrapped.WrappedContext = ctx

		return handler(srv, wrapped)
	}
}

func grpcGatewayAuthnIdentity(ctx context.Context, authOptions *IdentityAuthOption) (*AuthnIdentityInfo, error) {
	accessOrgId, err := GetAccessOrgIdFromGrpcContext(ctx)
	if err != nil {
		tilog.Warn(ctx, "grpcGatewayAuthnIdentity GetAccessOrgIdFromGrpcContext error", zap.Any("err", err))
	}

	identityType, identityVal, err := getIdentityTypeValueFromGrpcGw(ctx)
	if err != nil {
		return nil, err
	}
	in := IdentityAuthParam{
		IdentityType:  identityType,
		IdentityValue: identityVal,
	}
	if authOptions == nil {
		in.Options = IdentityAuthOption{}
	} else {
		in.Options = *authOptions
	}
	in.Options.AccessOrgID = accessOrgId

	ret, err := AuthnIdentity(ctx, &in)
	if err != nil {
		return nil, err
	}
	if ret.AuthRet != AuthnRetOK {
		return nil, errors.New(ret.AuthMessage)
	}
	return ret.IdentityInfo, nil
}

func getIdentityTypeValueFromGrpcGw(ctx context.Context) (AuthnIdentityType, string, error) {
	authorizationHeader := GetHttpMetadataFromGrpcContext(ctx, GrpcGatewayMetadataHeader+strings.ToLower(AuthorizationHeader))
	if authorizationHeader != "" {
		identityType := AuthnIdentityTypeUserToken
		identityVal, err := parseIdentityValue(identityType, authorizationHeader)
		return identityType, identityVal, err
	}

	authMethodHeader := GetHttpMetadataFromGrpcContext(ctx, GrpcGatewayMetadataHeader+strings.ToLower(XAuthMethodHeader))
	if authMethodHeader == XAuthMethodValBear {
		authRawHeader := GetHttpMetadataFromGrpcContext(ctx, GrpcGatewayMetadataHeader+strings.ToLower(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 := GetHttpMetadataFromGrpcContext(ctx, GrpcGatewayMetadataHeader+strings.ToLower(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))
}
