//revive:disable:var-naming

package grpc_http

//revive:enable:var-naming

import (
	"context"
	"fmt"
	"net/http"

	"github.com/gogo/grpc-example/insecure"
	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"github.com/rs/cors"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

func clientAlwaysLog(_ context.Context, _ string) bool {
	return true
}

// DialLogOptions logs the requests and response.
func DialLogOptions(logger *zap.Logger, opts GatewayOptions) []grpc.DialOption {
	var unary []grpc.UnaryClientInterceptor
	var stream []grpc.StreamClientInterceptor
	if opts.LogPayload {
		unary = append(unary,
			grpc_zap.PayloadUnaryClientInterceptor(logger, clientAlwaysLog),
		)
		stream = append(stream,
			grpc_zap.PayloadStreamClientInterceptor(logger, clientAlwaysLog),
		)
	}

	return []grpc.DialOption{
		grpc.WithUnaryInterceptor(
			grpc_middleware.ChainUnaryClient(unary...),
		),
		grpc.WithStreamInterceptor(
			grpc_middleware.ChainStreamClient(stream...),
		),
	}
}

// GatewayOptions has configuration options for the GRPC gateway
type GatewayOptions struct {
	LogPayload bool
}

// LocalGatewayOptions has Default GatewayOptions for local development
var LocalGatewayOptions = GatewayOptions{LogPayload: true}

// ProdGatewayOptions has default GatewayOptions for production
var ProdGatewayOptions = GatewayOptions{LogPayload: false}

// GatewayConn create a connection that can be used by a gateway
func GatewayConn(logger *zap.Logger, addr string, opts GatewayOptions) (*grpc.ClientConn, error) {
	// See https://github.com/grpc/grpc/blob/master/doc/naming.md
	// for gRPC naming standard information.
	dialAddr := fmt.Sprintf("passthrough://0.0.0.0/%s", addr)
	dialOpts := append(DialLogOptions(logger, opts),
		grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(insecure.CertPool, "")),
		grpc.WithBlock(),
	)
	return grpc.DialContext(
		context.Background(),
		dialAddr,
		// grpc.WithInsecure(),
		dialOpts...,
	)
}

// ConfigureGateway converts a mux to a http server for a GRPC gateway
func ConfigureGateway(mux http.Handler, gatewayPort int) http.Server {
	gatewayAddr := fmt.Sprintf("0.0.0.0:%d", gatewayPort)
	return http.Server{
		Handler: mux,
		Addr:    gatewayAddr,
	}
}

// RegisterHandler registers for the GRPC HTTP Gateway
type RegisterHandler func(context.Context, *runtime.ServeMux, *grpc.ClientConn) error

var corsOptsAllowAll = cors.Options{
	AllowedOrigins: []string{"*"},
	AllowedMethods: []string{"HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"},
	AllowedHeaders: []string{"*"},
	// Not sure how to get the frontend to not send the Authorization header
	// AllowCredentials: true,
	// AllowedHeaders: []string{"Authorization", "Content-Type", "Authorization", "Accept", "Origin", "Referer", "User-Agent"},
}

// CorsAllowAll is Cors configured to allow all headers, origins, methods, and allow credentials
var CorsAllowAll = cors.New(corsOptsAllowAll)

// Middleware is an HTTP middleware
type Middleware = func(http.Handler) http.Handler

// ServeGateway serves the GRPC gateway
type ServeGateway struct {
	Mux *http.ServeMux
}

// HTTP serves as HTTP
func (s ServeGateway) HTTP() (string, func() error) {
	gatewayPort := 11000
	gwServer := ConfigureGateway(s.Mux, gatewayPort)
	serve := func() error { return gwServer.ListenAndServe() }
	return gwServer.Addr, serve
}
