package controller

import (
	"context"
	"encoding/json"
	"errors"
	"log"
	"time"

	"github.com/gravitl/netmaker/functions"
	nodepb "github.com/gravitl/netmaker/grpc"
	"github.com/gravitl/netmaker/logger"
	"github.com/gravitl/netmaker/logic"
	"github.com/gravitl/netmaker/models"
	"github.com/gravitl/netmaker/mq"
	"github.com/gravitl/netmaker/servercfg"
	"github.com/gravitl/netmaker/serverctl"
)

// NodeServiceServer - represents the service server for gRPC
type NodeServiceServer struct {
	nodepb.UnimplementedNodeServiceServer
}

// NodeServiceServer.ReadNode - reads node and responds with gRPC
func (s *NodeServiceServer) ReadNode(ctx context.Context, req *nodepb.Object) (*nodepb.Object, error) {
	var node, err = getNodeFromRequestData(req.Data)
	if err != nil {
		return nil, err
	}

	node.NetworkSettings, err = logic.GetNetworkSettings(node.Network)
	if err != nil {
		return nil, err
	}
	getServerAddrs(&node)

	node.SetLastCheckIn()
	// Cast to ReadNodeRes type
	nodeData, errN := json.Marshal(&node)
	if errN != nil {
		return nil, err
	}
	logic.UpdateNode(&node, &node)
	response := &nodepb.Object{
		Data: string(nodeData),
		Type: nodepb.NODE_TYPE,
	}
	return response, nil
}

// NodeServiceServer.CreateNode - creates a node and responds over gRPC
func (s *NodeServiceServer) CreateNode(ctx context.Context, req *nodepb.Object) (*nodepb.Object, error) {
	var node = models.Node{}
	var err error
	data := req.GetData()
	if err := json.Unmarshal([]byte(data), &node); err != nil {
		return nil, err
	}

	validKey := logic.IsKeyValid(node.Network, node.AccessKey)
	node.NetworkSettings, err = logic.GetNetworkSettings(node.Network)
	if err != nil {
		return nil, err
	}
	log.Println("DELETE ME: Operating System = " + node.OS)
	if !validKey {
		if node.NetworkSettings.AllowManualSignUp == "yes" {
			node.IsPending = "yes"
		} else {
			return nil, errors.New("invalid key, and network does not allow no-key signups")
		}
	}
	unique, _ := functions.IsMacAddressUnique(node.MacAddress, node.Network)
	if !unique {
		return nil, errors.New("macaddress is not unique")
	}

	getServerAddrs(&node)

	key, keyErr := logic.RetrievePublicTrafficKey()
	if keyErr != nil {
		logger.Log(0, "error retrieving key: ", keyErr.Error())
		return nil, keyErr
	}

	node.TrafficKeys = models.TrafficKeys{
		Mine:   node.TrafficKeys.Mine,
		Server: key,
	}

	err = logic.CreateNode(&node)
	if err != nil {
		return nil, err
	}

	nodeData, errN := json.Marshal(&node)
	if errN != nil {
		return nil, err
	}

	response := &nodepb.Object{
		Data: string(nodeData),
		Type: nodepb.NODE_TYPE,
	}

	runUpdates(&node, false)

	go func(node *models.Node) {
		if node.UDPHolePunch == "yes" {
			var currentServerNode, getErr = logic.GetNetworkServerLeader(node.Network)
			if getErr != nil {
				return
			}
			for i := 0; i < 5; i++ {
				if logic.HasPeerConnected(node) {
					if logic.ShouldPublishPeerPorts(&currentServerNode) {
						err = mq.PublishPeerUpdate(&currentServerNode)
						if err != nil {
							logger.Log(1, "error publishing port updates when node", node.Name, "joined")
						}
						break
					}
				}
				time.Sleep(time.Second << 1) // allow time for client to startup
			}
		}
	}(&node)

	return response, nil
}

// NodeServiceServer.UpdateNode updates a node and responds over gRPC
func (s *NodeServiceServer) UpdateNode(ctx context.Context, req *nodepb.Object) (*nodepb.Object, error) {

	var newnode models.Node
	if err := json.Unmarshal([]byte(req.GetData()), &newnode); err != nil {
		return nil, err
	}

	node, err := logic.GetNodeByID(newnode.ID)
	if err != nil {
		return nil, err
	}

	if !servercfg.GetRce() {
		newnode.PostDown = node.PostDown
		newnode.PostUp = node.PostUp
	}

	err = logic.UpdateNode(&node, &newnode)
	if err != nil {
		return nil, err
	}
	newnode.NetworkSettings, err = logic.GetNetworkSettings(node.Network)
	if err != nil {
		return nil, err
	}
	getServerAddrs(&newnode)

	nodeData, errN := json.Marshal(&newnode)
	if errN != nil {
		return nil, err
	}

	runUpdates(&newnode, false)

	return &nodepb.Object{
		Data: string(nodeData),
		Type: nodepb.NODE_TYPE,
	}, nil
}

func getServerAddrs(node *models.Node) {
	serverNodes := logic.GetServerNodes(node.Network)
	//pubIP, _ := servercfg.GetPublicIP()
	if len(serverNodes) == 0 {
		if err := serverctl.SyncServerNetwork(node.Network); err != nil {
			return
		}
	}

	var serverAddrs = make([]models.ServerAddr, 1)

	for _, node := range serverNodes {

		serverAddrs = append(serverAddrs, models.ServerAddr{
			IsLeader: logic.IsLeader(&node),
			Address:  node.Address,
		})
	}

	networkSettings, _ := logic.GetParentNetwork(node.Network)
	// TODO consolidate functionality around files
	networkSettings.NodesLastModified = time.Now().Unix()
	networkSettings.DefaultServerAddrs = serverAddrs
	if err := logic.SaveNetwork(&networkSettings); err != nil {
		logger.Log(1, "unable to save network on serverAddr update", err.Error())
	}
	node.NetworkSettings.DefaultServerAddrs = networkSettings.DefaultServerAddrs
}

// NodeServiceServer.DeleteNode - deletes a node and responds over gRPC
func (s *NodeServiceServer) DeleteNode(ctx context.Context, req *nodepb.Object) (*nodepb.Object, error) {

	var node, err = getNodeFromRequestData(req.Data)
	if err != nil {
		return nil, err
	}

	err = logic.DeleteNodeByID(&node, true)
	if err != nil {
		return nil, err
	}
	runServerPeerUpdate(&node, false)

	return &nodepb.Object{
		Data: "success",
		Type: nodepb.STRING_TYPE,
	}, nil
}

// NodeServiceServer.GetPeers - fetches peers over gRPC
func (s *NodeServiceServer) GetPeers(ctx context.Context, req *nodepb.Object) (*nodepb.Object, error) {

	var node, err = getNodeFromRequestData(req.Data)
	if err != nil {
		return nil, err
	}

	excludeIsRelayed := node.IsRelay != "yes"
	var relayedNode string
	if node.IsRelayed == "yes" {
		relayedNode = node.Address
	}
	peers, err := logic.GetPeersList(node.Network, excludeIsRelayed, relayedNode)
	if err != nil {
		return nil, err
	}

	peersData, err := json.Marshal(&peers)
	logger.Log(3, node.Address, "checked in successfully")
	return &nodepb.Object{
		Data: string(peersData),
		Type: nodepb.NODE_TYPE,
	}, err
}

// NodeServiceServer.GetExtPeers - returns ext peers for a gateway node
func (s *NodeServiceServer) GetExtPeers(ctx context.Context, req *nodepb.Object) (*nodepb.Object, error) {

	var node, err = getNodeFromRequestData(req.Data)
	if err != nil {
		return nil, err
	}

	peers, err := logic.GetExtPeersList(&node)
	if err != nil {
		return nil, err
	}
	var extPeers []models.Node
	for i := 0; i < len(peers); i++ {
		extPeers = append(extPeers, models.Node{
			Address:             peers[i].Address,
			Address6:            peers[i].Address6,
			Endpoint:            peers[i].Endpoint,
			PublicKey:           peers[i].PublicKey,
			PersistentKeepalive: peers[i].KeepAlive,
			ListenPort:          peers[i].ListenPort,
			LocalAddress:        peers[i].LocalAddress,
		})
	}

	extData, err := json.Marshal(&extPeers)
	if err != nil {
		return nil, err
	}

	return &nodepb.Object{
		Data: string(extData),
		Type: nodepb.EXT_PEER,
	}, nil
}

// == private methods ==

func getNodeFromRequestData(data string) (models.Node, error) {
	var reqNode models.Node
	var err error

	if err = json.Unmarshal([]byte(data), &reqNode); err != nil {
		return models.Node{}, err
	}
	return logic.GetNodeByID(reqNode.ID)
}

func isServer(node *models.Node) bool {
	return node.IsServer == "yes"
}
