package store

import (
	"errors"

	"github.com/google/uuid"
)

type storableConstraint interface {
	ID() uuid.UUID
	Name() string
}

// GenericStore provides a in-memory implementation for multiple stores.
type GenericStore[T storableConstraint] struct {
	Store           map[uuid.UUID]T
	nameLookupTable map[string]uuid.UUID
}

// NewGenericStore returns a specific in-memory store for a type T.
func NewGenericStore[T storableConstraint]() GenericStore[T] {
	return GenericStore[T]{
		Store:           make(map[uuid.UUID]T),
		nameLookupTable: make(map[string]uuid.UUID),
	}
}

func (t *GenericStore[T]) Add(item T) error {
	_, ok := t.Store[item.ID()]
	if ok {
		return errors.New("item not found")
	}

	t.Store[item.ID()] = item
	t.nameLookupTable[item.Name()] = item.ID()

	return nil
}

func (t *GenericStore[T]) Update(item T) error {
	_, ok := t.Store[item.ID()]
	if ok {
		return nil
	}

	t.Store[item.ID()] = item
	t.nameLookupTable[item.Name()] = item.ID()

	return nil
}

func (t *GenericStore[T]) Delete(item T) error {
	delete(t.Store, item.ID())

	return nil
}

func (t *GenericStore[T]) Get(query Query) (T, error) {
	// First search for direct hit on UUID.
	item, ok := t.Store[query.ID]
	if !ok {
		// Second search for name
		id, ok := t.nameLookupTable[query.Name]
		if !ok {
			return *new(T), errors.New("item not found")
		}

		item, ok := t.Store[id]
		if !ok {
			return *new(T), errors.New("item not found")
		}

		return item, nil
	}

	return item, nil
}

func (t *GenericStore[T]) GetAll() ([]T, error) {
	var allItems []T

	for _, item := range t.Store {
		allItems = append(allItems, item)
	}

	return allItems, nil
}
