/*
 * Copyright 2021 CloudWeGo Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package nphttp2

import (
	"errors"
	"fmt"

	"github.com/cloudwego/kitex/pkg/kerrors"
	"github.com/cloudwego/kitex/pkg/remote"
	"github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/codes"
	"github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/status"
)

// only kitex errors will be converted
var kitexErrConvTab = map[error]codes.Code{
	kerrors.ErrInternalException: codes.Internal,
	kerrors.ErrOverlimit:         codes.ResourceExhausted,
	kerrors.ErrRemoteOrNetwork:   codes.Unavailable,
	kerrors.ErrACL:               codes.PermissionDenied,
}

// convert error to *status.Status
func convertStatus(err error) *status.Status {
	if err == nil {
		return status.New(codes.OK, "")
	}
	// covert from kitex error
	if kerrors.IsKitexError(err) {
		var basicError error
		var detailedError *kerrors.DetailedError
		if errors.As(err, &detailedError) {
			basicError = detailedError.ErrorType()
			// biz error should add biz info which is convenient to be recognized by client side
			if st := getStatusForBizErr(detailedError); st != nil {
				return st
			}
		} else {
			basicError = err
		}
		if c, ok := kitexErrConvTab[basicError]; ok {
			return status.New(c, err.Error())
		}
	}
	// return GRPCStatus() if err is built with status.Error
	if se, ok := err.(interface{ GRPCStatus() *status.Status }); ok {
		return se.GRPCStatus()
	}
	// build status.Status with code if error is remote.TransError
	if te, ok := err.(*remote.TransError); ok {
		return status.New(codes.Code(te.TypeID()), err.Error())
	}
	return status.New(codes.Internal, err.Error())
}

func getStatusForBizErr(dErr *kerrors.DetailedError) *status.Status {
	if !dErr.Is(kerrors.ErrBiz) {
		return nil
	}
	bizErr := dErr.Unwrap()
	if se, ok := bizErr.(status.Iface); ok {
		gs := se.GRPCStatus()
		gs.AppendMessage(fmt.Sprintf("[%s]", kerrors.ErrBiz.Error())).Err()
		return gs
	}
	if te, ok := bizErr.(*remote.TransError); ok {
		return status.New(codes.Code(te.TypeID()), fmt.Sprintf("%s [%s]", bizErr.Error(), kerrors.ErrBiz.Error()))
	}
	return status.New(codes.Internal, fmt.Sprintf("%s [%s]", bizErr.Error(), kerrors.ErrBiz.Error()))
}
