package auth

import (
	"errors"
	"time"

	"secondbit.org/uuid"
)

var (
	ErrTokenNotFound      = errors.New("Token not found in TokenStore.")
	ErrTokenAlreadyExists = errors.New("Token already exists in TokenStore.")
)

type Token struct {
	AccessToken  string
	RefreshToken string
	Created      time.Time
	ExpiresIn    int32
	TokenType    string
	Scope        string
	ProfileID    uuid.ID
}

type TokenStore interface {
	GetToken(token string, refresh bool) (Token, error)
	SaveToken(token Token) error
	RemoveToken(token string) error
	GetTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error)
}

func (m *Memstore) GetToken(token string, refresh bool) (Token, error) {
	if refresh {
		t, err := m.lookupTokenByRefresh(token)
		if err != nil {
			return Token{}, err
		}
		token = t
	}
	m.tokenLock.RLock()
	defer m.tokenLock.RUnlock()
	result, ok := m.tokens[token]
	if !ok {
		return Token{}, ErrTokenNotFound
	}
	return result, nil
}

func (m *Memstore) SaveToken(token Token) error {
	m.tokenLock.Lock()
	defer m.tokenLock.Unlock()
	_, ok := m.tokens[token.AccessToken]
	if ok {
		return ErrTokenAlreadyExists
	}
	m.tokens[token.AccessToken] = token
	if token.RefreshToken != "" {
		m.refreshTokenLookup[token.RefreshToken] = token.AccessToken
	}
	if _, ok = m.profileTokenLookup[token.ProfileID.String()]; ok {
		m.profileTokenLookup[token.ProfileID.String()] = append(m.profileTokenLookup[token.ProfileID.String()], token.AccessToken)
	} else {
		m.profileTokenLookup[token.ProfileID.String()] = []string{token.AccessToken}
	}
	return nil
}

func (m *Memstore) RemoveToken(token string) error {
	m.tokenLock.Lock()
	defer m.tokenLock.Unlock()
	t, ok := m.tokens[token]
	if !ok {
		return ErrTokenNotFound
	}
	delete(m.tokens, token)
	if t.RefreshToken != "" {
		delete(m.refreshTokenLookup, t.RefreshToken)
	}
	pos := -1
	for p, item := range m.profileTokenLookup[t.ProfileID.String()] {
		if item == token {
			pos = p
			break
		}
	}
	if pos >= 0 {
		m.profileTokenLookup[t.ProfileID.String()] = append(m.profileTokenLookup[t.ProfileID.String()][:pos], m.profileTokenLookup[t.ProfileID.String()][pos+1:]...)
	}
	return nil
}

func (m *Memstore) GetTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
	ids, err := m.lookupTokensByProfileID(profileID.String())
	if err != nil {
		return []Token{}, err
	}
	if len(ids) > num+offset {
		ids = ids[offset : num+offset]
	} else if len(ids) > offset {
		ids = ids[offset:]
	} else {
		return []Token{}, nil
	}
	tokens := []Token{}
	for _, id := range ids {
		token, err := m.GetToken(id, false)
		if err != nil {
			return []Token{}, err
		}
		tokens = append(tokens, token)
	}
	return tokens, nil
}
