package sqldb

import (
	"github.com/featurebasedb/featurebase/v3/dax"
	"github.com/featurebasedb/featurebase/v3/dax/controller"
	"github.com/featurebasedb/featurebase/v3/dax/models"
	"github.com/featurebasedb/featurebase/v3/logger"
	"github.com/pkg/errors"
)

var _ controller.NodeService = (*nodeService)(nil)

func NewNodeService(log logger.Logger) *nodeService {
	if log == nil {
		log = logger.NopLogger
	}
	return &nodeService{
		log: log,
	}
}

type nodeService struct {
	log logger.Logger
}

func (n *nodeService) CreateNode(tx dax.Transaction, addr dax.Address, node *dax.Node) error {
	dt, ok := tx.(*DaxTransaction)
	if !ok {
		return dax.NewErrInvalidTransaction("*sqldb.DaxTransaction")
	}

	mnode := &models.Node{Address: node.Address}
	err := dt.C.Create(mnode)
	if err != nil {
		return errors.Wrap(err, "creating node")
	}

	nodeRoles := make(models.NodeRoles, len(node.RoleTypes))
	mnode.NodeRoles = nodeRoles
	for i, rt := range node.RoleTypes {
		mnode.NodeRoles[i] = models.NodeRole{
			NodeID: mnode.ID,
			Role:   rt,
		}
	}
	err = dt.C.Create(&(mnode.NodeRoles))
	if err != nil {
		return errors.Wrap(err, "creating node roles")
	}

	return nil
}

func (n *nodeService) ReadNode(tx dax.Transaction, addr dax.Address) (*dax.Node, error) {
	dt, ok := tx.(*DaxTransaction)
	if !ok {
		return nil, dax.NewErrInvalidTransaction("*sqldb.DaxTransaction")
	}

	node := &models.Node{}
	err := dt.C.Eager().Where("address = ?", addr).First(node)
	if err != nil {
		return nil, errors.Wrap(err, "getting node")
	}

	roleTypes := make([]dax.RoleType, len(node.NodeRoles))
	for i, nr := range node.NodeRoles {
		roleTypes[i] = nr.Role
	}

	return &dax.Node{
		Address:   node.Address,
		RoleTypes: roleTypes,
	}, nil
}

func (n *nodeService) DeleteNode(tx dax.Transaction, addr dax.Address) error {
	dt, ok := tx.(*DaxTransaction)
	if !ok {
		return dax.NewErrInvalidTransaction("*sqldb.DaxTransaction")
	}

	node := &models.Node{}
	err := dt.C.Eager().Where("address = ?", addr).First(node)
	if isNoRowsError(err) {
		return nil
	} else if err != nil {
		return errors.Wrap(err, "finding node")
	}

	err = dt.C.Destroy(node)
	return errors.Wrap(err, "destroying node")
}

func (n *nodeService) Nodes(tx dax.Transaction) ([]*dax.Node, error) {
	dt, ok := tx.(*DaxTransaction)
	if !ok {
		return nil, dax.NewErrInvalidTransaction("*sqldb.DaxTransaction")
	}

	nodes := []*models.Node{}
	dt.C.Eager().Order("address asc").All(&nodes)

	ret := make([]*dax.Node, len(nodes))
	for i, node := range nodes {
		ret[i] = &dax.Node{
			Address:   node.Address,
			RoleTypes: make([]dax.RoleType, len(node.NodeRoles)),
		}
		for j, nr := range node.NodeRoles {
			ret[i].RoleTypes[j] = nr.Role
		}
	}

	return ret, nil
}
