package storage

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strings"
	"sync"
	"time"
)

type key struct {
	algorithm string
	public    []byte
}

type ver struct {
	version      int
	data         []byte
	createupdate time.Time
}

// 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
	checksums map[string]map[string]ver
}

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

// 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{}
			}
		}
	}
	version := ver{version: update.Version, data: update.Data, createupdate: time.Now()}
	st.tufMeta[id] = append(st.tufMeta[id], &version)
	checksumBytes := sha256.Sum256(update.Data)
	checksum := hex.EncodeToString(checksumBytes[:])

	_, ok := st.checksums[gun]
	if !ok {
		st.checksums[gun] = make(map[string]ver)
	}
	st.checksums[gun][checksum] = version
	return nil
}

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

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

// GetChecksum returns the createupdate date and metadata for a given role, under a GUN.
func (st *MemStorage) GetChecksum(gun, role, checksum string) (*time.Time, []byte, error) {
	st.lock.Lock()
	defer st.lock.Unlock()
	space, ok := st.checksums[gun][checksum]
	if !ok || len(space.data) == 0 {
		return nil, nil, ErrNotFound{}
	}
	return &(space.createupdate), space.data, nil
}

// Delete deletes 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)
		}
	}
	delete(st.checksums, gun)
	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)
}