package prefimpl

import (
	"context"
	"fmt"
	"sort"

	pref "github.com/grafana/grafana/pkg/services/preference"
)

// preferenceKey represents the primarily used index to access
// preferences.
//
// Preferences are typically either addressed by just OrgID;
// OrgID and UserID; or OrgID and UserID. Since all these IDs are one
// or more, using zero to represent "not relevant" replicates how the
// equivalent database index is structured.
type preferenceKey struct {
	OrgID  int64
	TeamID int64
	UserID int64
}

// inmemStore implements an implementation of store appropriate for
// testing without needing a full SQL database.
type inmemStore struct {
	preference map[preferenceKey]pref.Preference
	idMap      map[int64]preferenceKey
	nextID     int64
}

func (s *inmemStore) Get(ctx context.Context, preference *pref.Preference) (*pref.Preference, error) {
	res, ok := s.preference[preferenceKey{
		OrgID:  preference.OrgID,
		TeamID: preference.TeamID,
		UserID: preference.UserID,
	}]
	if !ok {
		return nil, pref.ErrPrefNotFound
	}

	return &res, nil
}

// List returns, in order, preferences relevant to the Organization,
// Org+Teams, and Org+User from the preference argument. The order is
// important, since later elements will override earlier when used
// in GetWithDefaults.
//
// Global preferences are not stored in the storage, but rather in the
// settings.Cfg structure, and are not returned by List.
func (s *inmemStore) List(ctx context.Context, preference *pref.Preference) ([]*pref.Preference, error) {
	res := []*pref.Preference{}

	// Org
	p, ok := s.preference[preferenceKey{
		OrgID: preference.OrgID,
	}]
	if ok {
		res = append(res, &p)
	}

	// Org + Teams (teams are numerically ordered)
	sort.Slice(preference.Teams, func(i, j int) bool {
		return preference.Teams[i] < preference.Teams[j]
	})

	for _, t := range preference.Teams {
		p, ok := s.preference[preferenceKey{
			OrgID:  preference.OrgID,
			TeamID: t,
		}]
		if !ok {
			continue
		}

		res = append(res, &p)
	}

	// Org + UserID
	if preference.UserID != 0 { // avoid adding the org preferences twice
		p, ok := s.preference[preferenceKey{
			OrgID:  preference.OrgID,
			UserID: preference.UserID,
		}]
		if ok {
			res = append(res, &p)
		}
	}

	return res, nil
}

func (s *inmemStore) Insert(ctx context.Context, preference *pref.Preference) (int64, error) {
	key := preferenceKey{
		OrgID:  preference.OrgID,
		TeamID: preference.TeamID,
		UserID: preference.UserID,
	}

	var p = *preference
	p.ID = s.nextID
	s.nextID++

	if _, exists := s.preference[key]; exists {
		return 0, fmt.Errorf("conflict in fake, preference for [orgid=%d, userid=%d, teamid=%d] already exists", preference.OrgID, preference.UserID, preference.TeamID)
	}

	s.preference[key] = p
	s.idMap[p.ID] = key
	return p.ID, nil
}

func (s *inmemStore) Update(ctx context.Context, preference *pref.Preference) error {
	key, ok := s.idMap[preference.ID]
	if !ok {
		return pref.ErrPrefNotFound
	}

	s.preference[key] = *preference
	return nil
}

func (s *inmemStore) Delete(context.Context, *pref.DeleteCommand) error {
	panic("not yet implemented")
}
