package goneo

import (
	"fmt"
	"github.com/neo4j/neo4j-go-driver/v4/neo4j/dbtype"
	"reflect"
)

type neo4jController struct {
	driver *neo4JDriver
}

func NewNeo4JController() *neo4jController {
	return new(neo4jController)
}

func NewNeo4JControllerAttached(driver *neo4JDriver) *neo4jController {
	return &neo4jController{driver}
}

func (u *neo4jController) Attach(driver *neo4JDriver) {
	u.driver = driver
}

func (u *neo4jController) Create(objs ...interface{}) (out []interface{}, err error) {
	for _, in := range objs {
		if isZero(in) {
			return nil, fmt.Errorf("the supplied object [%T] is not properly initialised", in)
		}
		record, err := u.driver.create(in)
		if err != nil {
			return nil, err
		}

		if record.Values == nil || len(record.Values) == 0 {
			return nil, fmt.Errorf("unable to create [models.neo4jController] entry with values [%v]", in)
		}
		out = append(out, record)
	}
	return out, err
}

func (u *neo4jController) Link(from interface{}, linkName string, tos ...interface{}) error {
	if isZero(from) || isZero(tos) {
		return fmt.Errorf("all provided objects need to be properly initialised")
	}
	for _, to := range tos {
		if err := u.driver.link(from, to, linkName); err != nil {
			return err
		}
	}
	return nil
}

func (u *neo4jController) List(out interface{}) error {
	mTyp := reflect.TypeOf(out)
	modelValue := reflect.ValueOf(out)
	if modelValue.Kind() != reflect.Ptr {
		return fmt.Errorf("a valid pointer struct must be passed")
	}
	modelType := mTyp.Elem()

	switch mTyp.Kind() {
	case reflect.Ptr:
		modelValue = modelValue.Elem()
		modelType = modelType.Elem()
	}

	records, err := u.driver.list(modelType.Name())
	if err != nil {
		return err
	}

	container := reflect.New(modelType).Interface()
	for _, r := range records {
		if err = unmarshal(r.Values[0].(dbtype.Node).Props, container); err != nil {
			return err
		}
		modelValue = reflect.Append(modelValue, reflect.ValueOf(container).Elem())
	}
	reflect.ValueOf(out).Elem().Set(modelValue)
	return nil
}

func (u *neo4jController) Delete(objs ...interface{}) error {
	for _, in := range objs {
		if isZero(in) {
			return fmt.Errorf("the supplied object [%T] is not properly initialised", in)
		}
		if err := u.driver.delete(in); err != nil {
			return err
		}
	}
	return nil
}

func (u *neo4jController) Find(in, out interface{}) error {
	mTyp := reflect.TypeOf(out)
	modelValue := reflect.ValueOf(out)
	if modelValue.Kind() != reflect.Ptr {
		return fmt.Errorf("a valid pointer struct must be passed")
	}
	modelType := mTyp.Elem()

	switch mTyp.Kind() {
	case reflect.Ptr:
		modelValue = modelValue.Elem()
		modelType = modelType.Elem()
	}

	records, err := u.driver.find(in)
	if err != nil {
		return err
	}

	container := reflect.New(modelType).Interface()
	for _, r := range records {
		if err = unmarshal(r.Values[0].(dbtype.Node).Props, container); err != nil {
			return err
		}
		modelValue = reflect.Append(modelValue, reflect.ValueOf(container).Elem())
	}
	reflect.ValueOf(out).Elem().Set(modelValue)
	return nil
}

func (u *neo4jController) GetLeafs(root interface{}, leafs interface{}, linkName string) error {
	if isZero(root) {
		return fmt.Errorf("the supplied object [%T] is not properly initialised", root)
	}

	leafType := asRefType(reflect.TypeOf(leafs))
	leafValue := asRef(reflect.ValueOf(leafs))

	records, err := u.driver.getLeafs(root, leafType.Elem().Name(), linkName)
	if err != nil {
		return err
	}

	container := reflect.New(leafType.Elem()).Interface()
	for _, r := range records {
		if err = unmarshal(r.Values[0].(dbtype.Node).Props, container); err != nil {
			return err
		}
		leafValue = reflect.Append(leafValue, reflect.ValueOf(container).Elem())
	}
	reflect.ValueOf(leafs).Elem().Set(leafValue)
	return nil
}