package auth

import (
	"errors"
	"sort"
	"time"

	"code.secondbit.org/uuid"
)

var (
	// ErrNoSessionStore is returned when a Context tries to act on a sessionStore without setting on first.
	ErrNoSessionStore = errors.New("no sessionStore was specified for the Context")
	// ErrSessionNotFound is returned when a Session is requested but not found in the sessionStore.
	ErrSessionNotFound = errors.New("session not found in sessionStore")
	// ErrInvalidSession is returned when a Session is specified but is not valid.
	ErrInvalidSession = errors.New("session is not valid")
	// ErrSessionAlreadyExists is returned when a sessionStore tries to store a Session with an ID that already exists in the sessionStore.
	ErrSessionAlreadyExists = errors.New("session already exists")
)

// Session represents a user's authenticated session, associating it with a profile
// and some audit data.
type Session struct {
	ID        string
	IP        string
	UserAgent string
	ProfileID uuid.ID
	Created   time.Time
	Login     string
	Active    bool
}

type sortedSessions []Session

func (s sortedSessions) Len() int {
	return len(s)
}

func (s sortedSessions) Less(i, j int) bool {
	return s[i].Created.After(s[j].Created)
}

func (s sortedSessions) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

type sessionStore interface {
	createSession(session Session) error
	getSession(id string) (Session, error)
	removeSession(id string) error
	listSessions(profile uuid.ID, before time.Time, num int64) ([]Session, error)
}

func (m *memstore) createSession(session Session) error {
	m.sessionLock.Lock()
	defer m.sessionLock.Unlock()
	if _, ok := m.sessions[session.ID]; ok {
		return ErrSessionAlreadyExists
	}
	m.sessions[session.ID] = session
	return nil
}

func (m *memstore) getSession(id string) (Session, error) {
	m.sessionLock.RLock()
	defer m.sessionLock.RUnlock()
	if _, ok := m.sessions[id]; !ok {
		return Session{}, ErrSessionNotFound
	}
	return m.sessions[id], nil
}

func (m *memstore) removeSession(id string) error {
	m.sessionLock.Lock()
	defer m.sessionLock.Unlock()
	if _, ok := m.sessions[id]; !ok {
		return ErrSessionNotFound
	}
	delete(m.sessions, id)
	return nil
}

func (m *memstore) listSessions(profile uuid.ID, before time.Time, num int64) ([]Session, error) {
	m.sessionLock.RLock()
	defer m.sessionLock.RUnlock()
	res := []Session{}
	for _, session := range m.sessions {
		if int64(len(res)) >= num {
			break
		}
		if profile != nil && !profile.Equal(session.ProfileID) {
			continue
		}
		if !before.IsZero() && session.Created.After(before) {
			continue
		}
		res = append(res, session)
	}
	sorted := sortedSessions(res)
	sort.Sort(sorted)
	res = []Session(sorted)
	return res, nil
}
