package controller

import (
	"context"
	"fmt"
	"net"

	"github.com/go-logr/logr"

	grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"go.uber.org/dig"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type GrpcController interface {
	Controller
	RegisterService(s *grpc.Server) error
}

type GprcServerParams struct {
	dig.In

	Conf        *config.RootConfig
	Logger      logr.Logger
	Controllers []Controller `group:"controller"`
}

type GrpcServer struct {
	conf config.GrpcServerConfig

	logger      logr.Logger
	controllers []GrpcController
	server      *grpc.Server
}

func NewGrpcServer(p GprcServerParams) (*GrpcServer, error) {
	grpcControllers := make([]GrpcController, 0)
	for _, ctrl := range p.Controllers {
		grpcCtrl, ok := ctrl.(GrpcController)
		if !ok {
			continue
		}
		grpcControllers = append(grpcControllers, grpcCtrl)
	}
	s := &GrpcServer{
		conf:        p.Conf.GrpcServer,
		logger:      p.Logger.WithName("GrpcServer"),
		controllers: grpcControllers,
	}

	server, err := setupGrpcServer()
	if err != nil {
		return nil, err
	}
	s.server = server
	err = s.initRoute()
	if err != nil {
		return nil, fmt.Errorf("init grpc server failed: %s", err)
	}
	return s, nil
}

func (s *GrpcServer) initRoute() error {
	for _, ctrl := range s.controllers {
		s.logger.Info("register grpc controller", "controller", ctrl.Name())
		err := ctrl.RegisterService(s.server)
		if err != nil {
			return fmt.Errorf("register controller failed: %s", err)
		}
	}
	return nil
}

func (s *GrpcServer) Run() error {
	listener, err := net.Listen("tcp", s.conf.ListenAddr)
	if err != nil {
		return fmt.Errorf("create listener failed: %s", err)
	}
	err = s.server.Serve(listener)
	if err != nil {
		return fmt.Errorf("grpc server failed: %s", err)
	}
	return nil
}

func (s *GrpcServer) Shutdown(ctx context.Context) error {
	s.logger.Info("grpc server is shutting down by caller")
	s.server.GracefulStop()
	return nil
}

func setupGrpcServer() (*grpc.Server, error) {
	return grpc.NewServer(
		grpc.ChainUnaryInterceptor(
			grpc_recovery.UnaryServerInterceptor(grpc_recovery.WithRecoveryHandler(grpcPanicRecoveryHandler())),
		),
	), nil
}

func grpcPanicRecoveryHandler() grpc_recovery.RecoveryHandlerFunc {
	return func(p interface{}) (err error) {
		return status.Errorf(codes.Unknown, "panic triggered: %v", p)
	}
}
