/*
 * Copyright (C) 2023-2024
 * Das Land Schleswig-Holstein vertreten durch den
 * Ministerpräsidenten des Landes Schleswig-Holstein
 * Staatskanzlei
 * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
 *
 * Lizenziert unter der EUPL, Version 1.2 oder - sobald
 * diese von der Europäischen Kommission genehmigt wurden -
 * Folgeversionen der EUPL ("Lizenz");
 * Sie dürfen dieses Werk ausschließlich gemäß
 * dieser Lizenz nutzen.
 * Eine Kopie der Lizenz finden Sie hier:
 *
 * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
 *
 * Sofern nicht durch anwendbare Rechtsvorschriften
 * gefordert oder in schriftlicher Form vereinbart, wird
 * die unter der Lizenz verbreitete Software "so wie sie
 * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
 * ausdrücklich oder stillschweigend - verbreitet.
 * Die sprachspezifischen Genehmigungen und Beschränkungen
 * unter der Lizenz sind dem Lizenztext zu entnehmen.
 */

package server

import (
	"context"
	"errors"
	pb "fachstellen-proxy/gen/go"
	"fachstellen-proxy/internal/config"
	"fachstellen-proxy/internal/mock"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
	"net"
)

const keyClientName = "CLIENT_NAME-bin"
const clientName = "FachstellenServer"

type collaborationRouter struct {
	pb.UnimplementedCollaborationServiceServer
}

func (s *collaborationRouter) FindVorgang(ctx context.Context, in *pb.GrpcFindVorgangRequest) (*pb.GrpcFindVorgangResponse, error) {
	response, err := s.handleRequest(ctx, func(client pb.CollaborationServiceClient, ctx context.Context) (interface{}, error) {
		return client.FindVorgang(ctx, in)
	})

	if response == nil {
		return nil, err
	}

	return response.(*pb.GrpcFindVorgangResponse), err
}

func (s *collaborationRouter) handleRequest(ctx context.Context, handler func(pb.CollaborationServiceClient, context.Context) (interface{}, error)) (interface{}, error) {
	logger.Info(fmt.Sprintf("setting grpc headers %s: %s", keyClientName, clientName))
	ctx = metadata.AppendToOutgoingContext(ctx, keyClientName, clientName)
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return nil, status.Error(codes.InvalidArgument, "unable to retrieve metadata")
	}

	grpcAddress := md.Get(GrpcAddressMetadata)
	if len(grpcAddress) == 0 {
		return nil, status.Error(codes.InvalidArgument, "grpc address is missing")
	}

	client, connCleanUp, err := createCollaborationClient(grpcAddress[0])
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	defer connCleanUp()

	return handler(client, ctx)
}

func createCollaborationClient(grpcAddress string) (pb.CollaborationServiceClient, func() error, error) {
	target := GetCollaborationServerUrl(grpcAddress, conf)
	conn, err := grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		logger.Error("collaboration router failed to route: %v", err)

		return nil, nil, errors.New("collaboration router failed to route")
	}

	return pb.NewCollaborationServiceClient(conn), conn.Close, nil
}

func GetCollaborationServerUrl(collaborationServerAddress string, c config.Config) string {
	collaborationServerPort := c.Grpc.Collaboration.Server.Port
	if c.Grpc.Mock {
		collaborationServerPort = mock.GrpcMockPort
	}

	return fmt.Sprintf("%v:%d", collaborationServerAddress, collaborationServerPort)
}

func StartCollaborationRouter() *grpc.Server {
	s := grpc.NewServer()
	pb.RegisterCollaborationServiceServer(s, &collaborationRouter{})

	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", conf.Grpc.Collaboration.Router.Port))
	if err != nil {
		logger.Fatal("collaboration router failed to listen: %v", err)
	}

	logger.Info("collaboration router listening on port %d", conf.Grpc.Collaboration.Router.Port)
	if err := s.Serve(lis); err != nil {
		logger.Fatal("collaboration router failed to serve: %v", err)
	}

	return s
}