package authorize

import (
	"net/http"
	"strconv"
	"strings"

	envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
	"github.com/tniswong/go.rfcx/rfc7231"
	"google.golang.org/grpc/codes"
)

func isGRPCRequest(in *envoy_service_auth_v3.CheckRequest) bool {
	hdrs := in.GetAttributes().GetRequest().GetHttp().GetHeaders()
	if hdrs == nil {
		return false
	}
	return hdrs["content-type"] == "application/grpc" || strings.HasPrefix(hdrs["content-type"], "application/grpc+")
}

func isGRPCWebRequest(in *envoy_service_auth_v3.CheckRequest) bool {
	hdrs := in.GetAttributes().GetRequest().GetHttp().GetHeaders()
	if hdrs == nil {
		return false
	}

	v := getHeader(hdrs, "Accept")
	if v == "" {
		return false
	}

	accept, err := rfc7231.ParseAccept(v)
	if err != nil {
		return false
	}

	mediaType, _ := accept.MostAcceptable([]string{
		"text/html",
		"application/grpc-web-text",
	})
	return mediaType == "application/grpc-web-text"
}

func deniedResponseForGRPC(
	code int32, reason string, headers http.Header,
) *envoy_service_auth_v3.CheckResponse {
	headers.Set("Content-Type", "application/grpc+json")
	headers["grpc-status"] = []string{strconv.Itoa(int(httpStatusCodeToGRPCStatusCode(code)))}
	headers["grpc-message"] = []string{reason}
	return mkDeniedCheckResponse(code, headers, "")
}

func deniedResponseForGRPCWeb(
	code int32, reason string, headers http.Header,
) *envoy_service_auth_v3.CheckResponse {
	headers.Set("Content-Type", "application/grpc-web+json")
	headers["grpc-status"] = []string{strconv.Itoa(int(httpStatusCodeToGRPCStatusCode(code)))}
	headers["grpc-message"] = []string{reason}
	return mkDeniedCheckResponse(code, headers, "")
}

func httpStatusCodeToGRPCStatusCode(httpStatusCode int32) codes.Code {
	// from https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
	switch httpStatusCode {
	case http.StatusBadRequest:
		return codes.Internal
	case http.StatusUnauthorized:
		return codes.Unauthenticated
	case http.StatusForbidden:
		return codes.PermissionDenied
	case http.StatusNotFound:
		return codes.Unimplemented
	case http.StatusTooManyRequests,
		http.StatusBadGateway,
		http.StatusServiceUnavailable,
		http.StatusGatewayTimeout:
		return codes.Unavailable
	default:
		return codes.Unknown
	}
}