package service

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"net"

	"helmet/app/handler"
	"helmet/app/logger"
	"helmet/app/operator"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

type ServiceConfig struct {
	Handler  *handler.Handler
	Operator *operator.Operator
	Listener net.Listener
	X509Cert []byte
	X509Key  []byte
}

type Service struct {
	gsrv     *grpc.Server
	hand     *handler.Handler
	oper     *operator.Operator
	log      *logger.Logger
	listen   net.Listener
	x509Cert []byte
	x509Key  []byte
}

func NewService(conf *ServiceConfig) *Service {
	svc := Service{
		hand:     conf.Handler,
		oper:     conf.Operator,
		listen:   conf.Listener,
		x509Cert: conf.X509Cert,
		x509Key:  conf.X509Key,
	}
	svc.log = logger.NewLogger("service")

	return &svc
}

func (svc *Service) Run() error {
	var err error
	svc.log.Infof("Service run")

	tlsCert, err := tls.X509KeyPair(svc.x509Cert, svc.x509Key)
	if err != nil {
		return err
	}
	tlsConfig := tls.Config{
		Certificates:       []tls.Certificate{tlsCert},
		ClientAuth:         tls.NoClientCert,
		InsecureSkipVerify: true,
	}
	tlsCredentials := credentials.NewTLS(&tlsConfig)
	if err != nil {
		return err
	}
	interceptors := []grpc.UnaryServerInterceptor{
		svc.authInterceptor,
		svc.logInterceptor,
	}
	gsrvOpts := []grpc.ServerOption{
		grpc.Creds(tlsCredentials),
		grpc.ChainUnaryInterceptor(interceptors...),
	}
	svc.gsrv = grpc.NewServer(gsrvOpts...)

	svc.hand.Register(svc.gsrv)

	svc.log.Infof("Service listening at %v", svc.listen.Addr())
	err = svc.gsrv.Serve(svc.listen)
	if err != nil {
		return err
	}
	return err
}

func (svc *Service) authInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
	meta, _ := metadata.FromIncomingContext(ctx)
	usernameArr := meta["username"]
	passwordArr := meta["password"]
	if len(usernameArr) == 0 || len(passwordArr) == 0 {
		err := status.Errorf(codes.PermissionDenied, "Empty auth data")
		return nil, err
	}
	username := meta["username"][0]
	password := meta["password"][0]
	if !svc.oper.ValidateUser(username, password) {
		err := status.Errorf(codes.PermissionDenied, "Incorrect auth data")
		return nil, err
	}
	return handler(ctx, req)
}

func (svc *Service) logInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
	var err error
	svc.log.Debugf("Called method: %v", info.FullMethod)
	reqData, err := json.Marshal(req)
	if err == nil {
		svc.log.Debugf("Request: %s", string(reqData))
	}
	return handler(ctx, req)
}

func (svc *Service) Stop() {
	svc.log.Infof("Stopping service")
	svc.gsrv.GracefulStop()
}
