package kernel

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"sync"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/backoff"
	"google.golang.org/grpc/credentials/insecure"
)

type ServerBuilder func(grpc.ServiceRegistrar, string)

var (
	grpcServerInstance *grpc.Server
	httpServerInstance *http.Server
	listener           net.Listener
)

func StartGRPCServer(ctx context.Context, port int, sb ServerBuilder, opts ...grpc.ServerOption) (endpoint string, err error) {
	if grpcServerInstance != nil {
		err = errors.New("There can only be one GRPC server running at a time")
		return
	}

	endpoint = fmt.Sprintf("0.0.0.0:%d", port)
	listener, err = net.Listen("tcp", endpoint)
	if err != nil {
		endpoint = ""
		err = fmt.Errorf("could not start tcp listener: %w", err)
		return
	}

	grpcServerInstance = grpc.NewServer(opts...)

	sb(grpcServerInstance, endpoint)

	if err = grpcServerInstance.Serve(listener); err != nil {
		endpoint = ""
		err = fmt.Errorf("could not serve GRPC server over listener: %w", err)
		return
	}

	InfoLog.Printf("Listening for GRPC requests on %s", endpoint)
	return
}

func StopGRPCServer() error {
	TraceLog.Println("stopping GRPC server...")
	grpcServerInstance.GracefulStop()
	return nil
}

func StartHTTPServer(ctx context.Context, port int, handler http.Handler) (err error) {
	if httpServerInstance != nil {
		err = errors.New("There can only be one HTTP server running at a time")
		return
	}

	endpoint := fmt.Sprintf("0.0.0.0:%d", port)
	listener, err = net.Listen("tcp", endpoint)
	if err != nil {
		err = fmt.Errorf("could not start tcp listener: %w", err)
		return
	}

	httpServerInstance = &http.Server{
		Handler:           handler,
		Addr:              endpoint,
		ReadHeaderTimeout: time.Second * 1,
	}

	if err = httpServerInstance.Serve(listener); err != nil {
		err = fmt.Errorf("could not serve http over listener: %w", err)
	}

	InfoLog.Printf("Listening for HTTP requests on %s", endpoint)
	return
}

func StopHTTPServer() error {
	if httpServerInstance == nil {
		return nil
	}

	TraceLog.Println("stopping HTTP Server...")
	ctx, canceler := context.WithDeadline(context.Background(), time.Now().Add(time.Second*3))
	defer canceler()
	return httpServerInstance.Shutdown(ctx)
}

type connMan struct {
	sync.Mutex
	openConns map[string]*grpc.ClientConn
}

var grpcConns = &connMan{
	openConns: map[string]*grpc.ClientConn{},
}

type DialConfig struct {
	InsecureTransport bool
	BackoffMaxDelay   time.Duration
	BackoffMinTimeout time.Duration
}

func DialGRPC(endpoint string, opts ...grpc.DialOption) (conn *grpc.ClientConn, err error) {
	grpcConns.Lock()
	defer grpcConns.Unlock()

	var ok bool
	if conn, ok = grpcConns.openConns[endpoint]; ok {
		conn.ResetConnectBackoff()
		return
	}

	conn, err = grpc.Dial(endpoint,
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithConnectParams(grpc.ConnectParams{
			Backoff: backoff.Config{
				MaxDelay: time.Second * 3,
			},
			MinConnectTimeout: time.Second,
		}))

	if err != nil {
		err = fmt.Errorf("Could not dial %s: %w", endpoint, err)
		return
	}
	grpcConns.openConns[endpoint] = conn

	return
}

func CloseGRPCConns() error {
	grpcServerInstance.GracefulStop()
	grpcConns.Lock()
	defer grpcConns.Unlock()
	for k, v := range grpcConns.openConns {
		TraceLog.Printf("closing grpc connection: %s", k)
		v.Close()
		defer delete(grpcConns.openConns, k)
	}
	return nil
}
