package server

import (
	"github.com/google/uuid"
	"hash/fnv"
	"strconv"
	"strings"
	"time"
)

const (
	// SetupKeyReusable is a multi-use key (can be used for multiple machines)
	SetupKeyReusable SetupKeyType = "reusable"
	// SetupKeyOneOff is a single use key (can be used only once)
	SetupKeyOneOff SetupKeyType = "one-off"

	// DefaultSetupKeyDuration = 1 month
	DefaultSetupKeyDuration = 24 * 30 * time.Hour
	// DefaultSetupKeyName is a default name of the default setup key
	DefaultSetupKeyName = "Default key"
)

// SetupKeyType is the type of setup key
type SetupKeyType string

// SetupKey represents a pre-authorized key used to register machines (peers)
type SetupKey struct {
	Id        string
	Key       string
	Name      string
	Type      SetupKeyType
	CreatedAt time.Time
	ExpiresAt time.Time
	// Revoked indicates whether the key was revoked or not (we don't remove them for tracking purposes)
	Revoked bool
	// UsedTimes indicates how many times the key was used
	UsedTimes int
	// LastUsed last time the key was used for peer registration
	LastUsed time.Time
}

//Copy copies SetupKey to a new object
func (key *SetupKey) Copy() *SetupKey {
	return &SetupKey{
		Id:        key.Id,
		Key:       key.Key,
		Name:      key.Name,
		Type:      key.Type,
		CreatedAt: key.CreatedAt,
		ExpiresAt: key.ExpiresAt,
		Revoked:   key.Revoked,
		UsedTimes: key.UsedTimes,
		LastUsed:  key.LastUsed,
	}
}

//IncrementUsage makes a copy of a key, increments the UsedTimes by 1 and sets LastUsed to now
func (key *SetupKey) IncrementUsage() *SetupKey {
	c := key.Copy()
	c.UsedTimes = c.UsedTimes + 1
	c.LastUsed = time.Now()
	return c
}

// IsValid is true if the key was not revoked, is not expired and used not more than it was supposed to
func (key *SetupKey) IsValid() bool {
	return !key.IsRevoked() && !key.IsExpired() && !key.IsOverUsed()
}

// IsRevoked if key was revoked
func (key *SetupKey) IsRevoked() bool {
	return key.Revoked
}

// IsExpired if key was expired
func (key *SetupKey) IsExpired() bool {
	return time.Now().After(key.ExpiresAt)
}

// IsOverUsed if key was used too many times
func (key *SetupKey) IsOverUsed() bool {
	return key.Type == SetupKeyOneOff && key.UsedTimes >= 1
}

// GenerateSetupKey generates a new setup key
func GenerateSetupKey(name string, t SetupKeyType, validFor time.Duration) *SetupKey {
	key := strings.ToUpper(uuid.New().String())
	createdAt := time.Now()
	return &SetupKey{
		Id:        strconv.Itoa(int(Hash(key))),
		Key:       key,
		Name:      name,
		Type:      t,
		CreatedAt: createdAt,
		ExpiresAt: createdAt.Add(validFor),
		Revoked:   false,
		UsedTimes: 0,
	}
}

// GenerateDefaultSetupKey generates a default setup key
func GenerateDefaultSetupKey() *SetupKey {
	return GenerateSetupKey(DefaultSetupKeyName, SetupKeyReusable, DefaultSetupKeyDuration)
}

func Hash(s string) uint32 {
	h := fnv.New32a()
	_, err := h.Write([]byte(s))
	if err != nil {
		panic(err)
	}
	return h.Sum32()
}
