package server

import (
	"context"
	"fmt"
	"net"
	"time"

	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
	grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/initialize"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/proto/customer"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/proto/payment_callback"
)

type DepsGRPC struct {
	Logger *zap.Logger
}

type GRPC struct {
	logger *zap.Logger
	grpc   *grpc.Server
}

func NewGRPC(deps DepsGRPC) (*GRPC, errors.Error) {
	if deps.Logger == nil {
		return nil, errors.NewWithMessage("Logger is nil on <NewGRPC>", errors.ErrInvalidArgs)
	}

	grpcStreamInterceptor := grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
		grpc_zap.StreamServerInterceptor(deps.Logger),
		grpc_recovery.StreamServerInterceptor(),
	))

	grpcUnaryInterceptor := grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
		grpc_zap.UnaryServerInterceptor(deps.Logger),
		grpc_recovery.UnaryServerInterceptor(),
	))

	return &GRPC{
		grpc:   grpc.NewServer(grpcStreamInterceptor, grpcUnaryInterceptor, grpc.ConnectionTimeout(5*time.Second)),
		logger: deps.Logger,
	}, nil
}

func (receiver *GRPC) Run(config *models.ConfigurationGRPC) {
	connectionString := fmt.Sprintf("%s:%s", config.Host, config.Port)

	receiver.logger.Info("Starting GRPC Server", zap.String("host", connectionString))

	if err := receiver.listen(connectionString); err != nil && err != grpc.ErrServerStopped {
		receiver.logger.Error("GRPC Listen error", zap.Error(err))
	}
}

func (receiver *GRPC) Stop(_ context.Context) error {
	receiver.grpc.GracefulStop()
	receiver.logger.Info("Shutting down GRPC server...")

	return nil
}

func (receiver *GRPC) Register(controllers *initialize.RpcControllers) *GRPC {
	payment_callback.RegisterPaymentCallbackServiceServer(receiver.grpc, controllers.PaymentController)
	customer.RegisterCustomerServiceServer(receiver.grpc, controllers.CustomerController)

	return receiver
}

func (receiver *GRPC) listen(address string) error {
	listener, err := net.Listen("tcp", address)
	if err != nil {
		return err
	}

	return receiver.grpc.Serve(listener)
}
