package internal

import (
	"context"
	"net/url"

	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/netbirdio/netbird/client/ssh"
	"github.com/netbirdio/netbird/client/system"
	mgm "github.com/netbirdio/netbird/management/client"
	mgmProto "github.com/netbirdio/netbird/management/proto"
)

// IsLoginRequired check that the server is support SSO or not
func IsLoginRequired(ctx context.Context, config *Config) (bool, error) {
	mgmURL := config.ManagementURL
	mgmClient, err := getMgmClient(ctx, config.PrivateKey, mgmURL)
	if err != nil {
		return false, err
	}
	defer func() {
		err = mgmClient.Close()
		if err != nil {
			cStatus, ok := status.FromError(err)
			if !ok || ok && cStatus.Code() != codes.Canceled {
				log.Warnf("failed to close the Management service client, err: %v", err)
			}
		}
	}()
	log.Debugf("connected to the Management service %s", mgmURL.String())

	pubSSHKey, err := ssh.GeneratePublicKey([]byte(config.SSHKey))
	if err != nil {
		return false, err
	}

	_, err = doMgmLogin(ctx, mgmClient, pubSSHKey, config)
	if isLoginNeeded(err) {
		return true, nil
	}
	return false, err
}

// Login or register the client
func Login(ctx context.Context, config *Config, setupKey string, jwtToken string) error {
	mgmClient, err := getMgmClient(ctx, config.PrivateKey, config.ManagementURL)
	if err != nil {
		return err
	}
	defer func() {
		err = mgmClient.Close()
		if err != nil {
			cStatus, ok := status.FromError(err)
			if !ok || ok && cStatus.Code() != codes.Canceled {
				log.Warnf("failed to close the Management service client, err: %v", err)
			}
		}
	}()
	log.Debugf("connected to the Management service %s", config.ManagementURL.String())

	pubSSHKey, err := ssh.GeneratePublicKey([]byte(config.SSHKey))
	if err != nil {
		return err
	}

	serverKey, err := doMgmLogin(ctx, mgmClient, pubSSHKey, config)
	if serverKey != nil && isRegistrationNeeded(err) {
		log.Debugf("peer registration required")
		_, err = registerPeer(ctx, *serverKey, mgmClient, setupKey, jwtToken, pubSSHKey, config)
		return err
	}

	return err
}

func getMgmClient(ctx context.Context, privateKey string, mgmURL *url.URL) (*mgm.GrpcClient, error) {
	// validate our peer's Wireguard PRIVATE key
	myPrivateKey, err := wgtypes.ParseKey(privateKey)
	if err != nil {
		log.Errorf("failed parsing Wireguard key %s: [%s]", privateKey, err.Error())
		return nil, err
	}

	var mgmTlsEnabled bool
	if mgmURL.Scheme == "https" {
		mgmTlsEnabled = true
	}

	log.Debugf("connecting to the Management service %s", mgmURL.String())
	mgmClient, err := mgm.NewClient(ctx, mgmURL.Host, myPrivateKey, mgmTlsEnabled)
	if err != nil {
		log.Errorf("failed connecting to the Management service %s %v", mgmURL.String(), err)
		return nil, err
	}
	return mgmClient, err
}

func doMgmLogin(ctx context.Context, mgmClient *mgm.GrpcClient, pubSSHKey []byte, config *Config) (*wgtypes.Key, error) {
	serverKey, err := mgmClient.GetServerPublicKey()
	if err != nil {
		log.Errorf("failed while getting Management Service public key: %v", err)
		return nil, err
	}

	sysInfo := system.GetInfo(ctx)
	sysInfo.SetFlags(
		config.RosenpassEnabled,
		config.RosenpassPermissive,
		config.ServerSSHAllowed,
		config.DisableClientRoutes,
		config.DisableServerRoutes,
		config.DisableDNS,
		config.DisableFirewall,
	)
	_, err = mgmClient.Login(*serverKey, sysInfo, pubSSHKey, config.DNSLabels)
	return serverKey, err
}

// registerPeer checks whether setupKey was provided via cmd line and if not then it prompts user to enter a key.
// Otherwise tries to register with the provided setupKey via command line.
func registerPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string, jwtToken string, pubSSHKey []byte, config *Config) (*mgmProto.LoginResponse, error) {
	validSetupKey, err := uuid.Parse(setupKey)
	if err != nil && jwtToken == "" {
		return nil, status.Errorf(codes.InvalidArgument, "invalid setup-key or no sso information provided, err: %v", err)
	}

	log.Debugf("sending peer registration request to Management Service")
	info := system.GetInfo(ctx)
	info.SetFlags(
		config.RosenpassEnabled,
		config.RosenpassPermissive,
		config.ServerSSHAllowed,
		config.DisableClientRoutes,
		config.DisableServerRoutes,
		config.DisableDNS,
		config.DisableFirewall,
	)
	loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), jwtToken, info, pubSSHKey, config.DNSLabels)
	if err != nil {
		log.Errorf("failed registering peer %v,%s", err, validSetupKey.String())
		return nil, err
	}

	log.Infof("peer has been successfully registered on Management Service")

	return loginResp, nil
}

func isLoginNeeded(err error) bool {
	if err == nil {
		return false
	}
	s, ok := status.FromError(err)
	if !ok {
		return false
	}
	if s.Code() == codes.InvalidArgument || s.Code() == codes.PermissionDenied {
		return true
	}
	return false
}

func isRegistrationNeeded(err error) bool {
	if err == nil {
		return false
	}
	s, ok := status.FromError(err)
	if !ok {
		return false
	}
	if s.Code() == codes.PermissionDenied {
		return true
	}
	return false
}
