package auth

import (
	"errors"
	"fmt"
	"sort"
)

var (
	ErrNoScopeStore = errors.New("scopeStore not set in Context")
)

type ErrScopeNotFound struct {
	Pos int
	ID  string
}

func (e ErrScopeNotFound) Error() string {
	return fmt.Sprintf("scope %s couldn't be found", e.ID)
}

type ErrScopeAlreadyExists struct {
	Pos int
	ID  string
}

func (e ErrScopeAlreadyExists) Error() string {
	return fmt.Sprintf("scope %s already exists", e.ID)
}

// Scope represents a limit on the access that a grant provides.
type Scope struct {
	ID          string
	Name        string
	Description string
}

func (s *Scope) ApplyChange(change ScopeChange) {
	if change.Name != nil {
		s.Name = *change.Name
	}
	if change.Description != nil {
		s.Description = *change.Description
	}
}

type sortedScopes []Scope

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

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

func (s sortedScopes) Less(i, j int) bool {
	return s[i].ID < s[j].ID
}

// ScopeChange represents a change to a Scope.
type ScopeChange struct {
	ID          string
	Name        *string
	Description *string
}

type scopeStore interface {
	createScopes(scopes []Scope) error
	getScopes(ids []string) ([]Scope, error)
	updateScopes(changes []ScopeChange) ([]Scope, error)
	removeScopes(ids []string) error
	listScopes() ([]Scope, error)
}

func (m *memstore) createScopes(scopes []Scope) error {
	m.scopeLock.Lock()
	defer m.scopeLock.Unlock()

	for pos, scope := range scopes {
		if _, ok := m.scopes[scope.ID]; ok {
			return ErrScopeAlreadyExists{Pos: pos, ID: scope.ID}
		}
	}
	for _, scope := range scopes {
		m.scopes[scope.ID] = scope
	}
	return nil
}

func (m *memstore) getScopes(ids []string) ([]Scope, error) {
	m.scopeLock.RLock()
	defer m.scopeLock.RUnlock()

	scopes := []Scope{}
	for pos, id := range ids {
		scope, ok := m.scopes[id]
		if !ok {
			return []Scope{}, ErrScopeNotFound{ID: id, Pos: pos}
		}
		scopes = append(scopes, scope)
	}
	sorted := sortedScopes(scopes)
	sort.Sort(sorted)
	scopes = sorted
	return scopes, nil
}

func (m *memstore) updateScopes(changes []ScopeChange) ([]Scope, error) {
	m.scopeLock.Lock()
	defer m.scopeLock.Unlock()

	scopes := []Scope{}

	for pos, change := range changes {
		if _, ok := m.scopes[change.ID]; !ok {
			return []Scope{}, ErrScopeNotFound{Pos: pos, ID: change.ID}
		}
	}
	for _, change := range changes {
		scope := m.scopes[change.ID]
		scope.ApplyChange(change)
		m.scopes[change.ID] = scope
		scopes = append(scopes, scope)
	}
	sorted := sortedScopes(scopes)
	sort.Sort(sorted)
	scopes = sorted
	return scopes, nil
}

func (m *memstore) removeScopes(ids []string) error {
	m.scopeLock.Lock()
	defer m.scopeLock.Unlock()

	for pos, id := range ids {
		if _, ok := m.scopes[id]; !ok {
			return ErrScopeNotFound{Pos: pos, ID: id}
		}
	}
	for _, id := range ids {
		delete(m.scopes, id)
	}
	return nil
}

func (m *memstore) listScopes() ([]Scope, error) {
	m.scopeLock.RLock()
	defer m.scopeLock.RUnlock()

	scopes := []Scope{}
	for _, scope := range m.scopes {
		scopes = append(scopes, scope)
	}
	sorted := sortedScopes(scopes)
	sort.Sort(sorted)
	scopes = sorted
	return scopes, nil
}
