package storage

import (
	"fmt"
	"strings"
	"sync"
)

type key struct {
	algorithm string
	public    []byte
}

type ver struct {
	version int
	data    []byte
}

// MemStorage is really just designed for dev and testing. It is very
// inefficient in many scenarios
type MemStorage struct {
	lock    sync.Mutex
	tufMeta map[string][]*ver
	keys    map[string]map[string]*key
}

// NewMemStorage instantiates a memStorage instance
func NewMemStorage() *MemStorage {
	return &MemStorage{
		tufMeta: make(map[string][]*ver),
		keys:    make(map[string]map[string]*key),
	}
}

// UpdateCurrent updates the meta data for a specific role
func (st *MemStorage) UpdateCurrent(gun string, update MetaUpdate) error {
	id := entryKey(gun, update.Role)
	st.lock.Lock()
	defer st.lock.Unlock()
	if space, ok := st.tufMeta[id]; ok {
		for _, v := range space {
			if v.version >= update.Version {
				return &ErrOldVersion{}
			}
		}
	}
	st.tufMeta[id] = append(st.tufMeta[id], &ver{version: update.Version, data: update.Data})
	return nil
}

// UpdateMany updates multiple TUF records
func (st *MemStorage) UpdateMany(gun string, updates []MetaUpdate) error {
	for _, u := range updates {
		st.UpdateCurrent(gun, u)
	}
	return nil
}

// GetCurrent returns the metadada for a given role, under a GUN
func (st *MemStorage) GetCurrent(gun, role string) (data []byte, err error) {
	id := entryKey(gun, role)
	st.lock.Lock()
	defer st.lock.Unlock()
	space, ok := st.tufMeta[id]
	if !ok || len(space) == 0 {
		return nil, ErrNotFound{}
	}
	return space[len(space)-1].data, nil
}

// Delete delets all the metadata for a given GUN
func (st *MemStorage) Delete(gun string) error {
	st.lock.Lock()
	defer st.lock.Unlock()
	for k := range st.tufMeta {
		if strings.HasPrefix(k, gun) {
			delete(st.tufMeta, k)
		}
	}
	return nil
}

// GetKey returns the public key material of the timestamp key of a given gun
func (st *MemStorage) GetKey(gun, role string) (algorithm string, public []byte, err error) {
	// no need for lock. It's ok to return nil if an update
	// wasn't observed
	g, ok := st.keys[gun]
	if !ok {
		return "", nil, &ErrNoKey{gun: gun}
	}
	k, ok := g[role]
	if !ok {
		return "", nil, &ErrNoKey{gun: gun}
	}

	return k.algorithm, k.public, nil
}

// SetKey sets a key under a gun and role
func (st *MemStorage) SetKey(gun, role, algorithm string, public []byte) error {
	k := &key{algorithm: algorithm, public: public}
	st.lock.Lock()
	defer st.lock.Unlock()

	// we hold the lock so nothing will be able to race to write a key
	// between checking and setting
	_, _, err := st.GetKey(gun, role)
	if _, ok := err.(*ErrNoKey); !ok {
		return &ErrKeyExists{gun: gun, role: role}
	}
	_, ok := st.keys[gun]
	if !ok {
		st.keys[gun] = make(map[string]*key)
	}
	st.keys[gun][role] = k
	return nil
}

func entryKey(gun, role string) string {
	return fmt.Sprintf("%s.%s", gun, role)
}