package main

import (
	"encoding/json"
	"errors"
	"time"

	"github.com/larkox/mattermost-plugin-badges/badgesmodel"
)

const ATOMICRETRIES = 3

func (s *store) doAtomic(f func() (bool, error)) error {
	done := false
	for i := 0; i < ATOMICRETRIES; i++ {
		var err error
		done, err = f()
		if err != nil {
			return err
		}
		if done {
			break
		}
	}
	if !done {
		return errors.New("too many attempts on atomic retry")
	}

	return nil
}

func (s *store) compareAndSet(key string, old []byte, value interface{}) (bool, error) {
	data, err := json.Marshal(value)
	if err != nil {
		return false, err
	}

	done, appErr := s.api.KVCompareAndSet(key, old, data)
	if appErr != nil {
		return false, appErr
	}

	return done, nil
}

func (s *store) atomicRemoveBadge(bID badgesmodel.BadgeID) (bool, error) {
	bb, data, err := s.getAllBadges()
	if err != nil {
		return false, err
	}

	for i, b := range bb {
		if b.ID == bID {
			bb = append(bb[:i], bb[i+1:]...)
			break
		}
	}

	return s.compareAndSet(KVKeyBadges, data, bb)
}

func (s *store) atomicRemoveBadgeFromOwnership(bID badgesmodel.BadgeID) (bool, error) {
	ownership, data, err := s.getOwnershipList()
	if err != nil {
		return false, err
	}

	toDelete := []int{}
	for i, o := range ownership {
		if o.Badge == bID {
			toDelete = append([]int{i}, toDelete...)
		}
	}

	for _, index := range toDelete {
		ownership = append(ownership[:index], ownership[index+1:]...)
	}

	return s.compareAndSet(KVKeyOwnership, data, ownership)
}

func (s *store) atomicAddBadge(b *badgesmodel.Badge) (bool, error) {
	bb, data, err := s.getAllBadges()
	if err != nil {
		return false, err
	}

	bb = append(bb, b)

	return s.compareAndSet(KVKeyBadges, data, bb)
}

func (s *store) atomicAddType(t *badgesmodel.BadgeTypeDefinition) (bool, error) {
	tt, data, err := s.getAllTypes()
	if err != nil {
		return false, err
	}

	tt = append(tt, t)

	return s.compareAndSet(KVKeyTypes, data, tt)
}

func (s *store) atomicAddBadgeToOwnership(o badgesmodel.Ownership, isMultiple bool) (shouldNotify bool, done bool, err error) {
	ownership, data, err := s.getOwnershipList()
	if err != nil {
		return false, false, err
	}

	if !isMultiple && ownership.IsOwned(o.User, o.Badge) {
		return false, true, errAlreadyOwned
	}

	ownership = append(ownership, o)

	done, err = s.compareAndSet(KVKeyOwnership, data, ownership)
	return done, done, err
}

func (s *store) atomicUpdateType(t *badgesmodel.BadgeTypeDefinition) (bool, error) {
	tt, data, err := s.getAllTypes()
	if err != nil {
		return false, err
	}

	found := false
	for i, tOld := range tt {
		if tOld.ID == t.ID {
			tt[i] = t
			found = true
			break
		}
	}

	if !found {
		return false, errors.New("not found")
	}

	return s.compareAndSet(KVKeyTypes, data, tt)
}

func (s *store) atomicUpdateBadge(b *badgesmodel.Badge) (bool, error) {
	bb, data, err := s.getAllBadges()
	if err != nil {
		return false, err
	}

	found := false
	for i, bOld := range bb {
		if bOld.ID == b.ID {
			bb[i] = b
			found = true
			break
		}
	}
	if !found {
		return false, errors.New("not found")
	}

	return s.compareAndSet(KVKeyBadges, data, bb)
}

func (s *store) atomicRevokeOwnership(badgeID badgesmodel.BadgeID, userID string, grantTime string) (bool, error) {
	ownership, data, err := s.getOwnershipList()
	if err != nil {
		return false, err
	}

	found := false
	for i, o := range ownership {
		if o.Badge == badgeID && o.User == userID && o.Time.Format(time.RFC3339Nano) == grantTime {
			ownership = append(ownership[:i], ownership[i+1:]...)
			found = true
			break
		}
	}

	if !found {
		return true, nil
	}

	return s.compareAndSet(KVKeyOwnership, data, ownership)
}

func (s *store) atomicAddSubscription(toAdd badgesmodel.Subscription) (bool, error) {
	subs, data, err := s.getAllSubscriptions()
	if err != nil {
		return false, err
	}

	for _, sub := range subs {
		if sub.ChannelID == toAdd.ChannelID && sub.TypeID == toAdd.TypeID {
			return true, nil
		}
	}

	subs = append(subs, toAdd)

	return s.compareAndSet(KVKeySubscriptions, data, subs)
}

func (s *store) atomicRemoveSubscription(toRemove badgesmodel.Subscription) (bool, error) {
	subs, data, err := s.getAllSubscriptions()
	if err != nil {
		return false, err
	}

	found := false
	for i, sub := range subs {
		if sub.ChannelID == toRemove.ChannelID && sub.TypeID == toRemove.TypeID {
			subs = append(subs[:i], subs[i+1:]...)
			found = true
			break
		}
	}

	if !found {
		return true, nil
	}

	return s.compareAndSet(KVKeySubscriptions, data, subs)
}
