package auth

import (
	"errors"

	"secondbit.org/uuid"
)

var (
	ErrClientNotFound      = errors.New("Client not found in ClientStore.")
	ErrClientAlreadyExists = errors.New("Client already exists in ClientStore.")
)

// Client represents a client that grants access
// to the auth server, exchanging grants for tokens,
// and tokens for access.
type Client struct {
	ID          uuid.ID
	Secret      string
	RedirectURI string
	OwnerID     uuid.ID
	Name        string
	Logo        string
	Website     string
}

type ClientChange struct {
	Secret      *string
	RedirectURI *string
	OwnerID     uuid.ID
	Name        *string
	Logo        *string
	Website     *string
}

// ClientStore abstracts the storage interface for
// storing and retrieving Clients.
type ClientStore interface {
	GetClient(id uuid.ID) (Client, error)
	SaveClient(client Client) error
	UpdateClient(id uuid.ID, change ClientChange) error
	DeleteClient(id uuid.ID) error
	ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error)
}

func (m *Memstore) GetClient(id uuid.ID) (Client, error) {
	m.clientLock.RLock()
	defer m.clientLock.RUnlock()
	c, ok := m.clients[id.String()]
	if !ok {
		return Client{}, ErrClientNotFound
	}
	return c, nil
}

func (m *Memstore) SaveClient(client Client) error {
	m.clientLock.Lock()
	defer m.clientLock.Unlock()
	if _, ok := m.clients[client.ID.String()]; ok {
		return ErrClientAlreadyExists
	}
	m.clients[client.ID.String()] = client
	m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()], client.ID)
	return nil
}

func (m *Memstore) UpdateClient(id uuid.ID, change ClientChange) error {
	return nil
}

func (m *Memstore) DeleteClient(id uuid.ID) error {
	client, err := m.GetClient(id)
	if err != nil {
		return err
	}
	m.clientLock.Lock()
	defer m.clientLock.Unlock()
	delete(m.clients, id.String())
	pos := -1
	for p, item := range m.profileClientLookup[client.OwnerID.String()] {
		if item.Equal(id) {
			pos = p
			break
		}
	}
	if pos >= 0 {
		m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()][:pos], m.profileClientLookup[client.OwnerID.String()][pos+1:]...)
	}
	return nil
}

func (m *Memstore) ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
	ids, err := m.lookupClientsByProfileID(ownerID.String())
	if err != nil {
		return []Client{}, err
	}
	if len(ids) > num+offset {
		ids = ids[offset : num+offset]
	} else if len(ids) > offset {
		ids = ids[offset:]
	} else {
		return []Client{}, nil
	}
	clients := []Client{}
	for _, id := range ids {
		client, err := m.GetClient(id)
		if err != nil {
			return []Client{}, err
		}
		clients = append(clients, client)
	}
	return clients, nil
}
