package multitenant

import (
	"sync"
	"time"

	consul "github.com/hashicorp/consul/api"
)

type mockKV struct {
	mtx  sync.Mutex
	cond *sync.Cond
	kvps map[string]*consul.KVPair
	next uint64 // the next update will have this 'index in the the log'
}

func newMockConsulClient() ConsulClient {
	m := mockKV{
		kvps: map[string]*consul.KVPair{},
	}
	m.cond = sync.NewCond(&m.mtx)
	go m.loop()
	return &consulClient{&m}
}

func copyKVPair(in *consul.KVPair) *consul.KVPair {
	value := make([]byte, len(in.Value))
	copy(value, in.Value)
	return &consul.KVPair{
		Key:         in.Key,
		CreateIndex: in.CreateIndex,
		ModifyIndex: in.ModifyIndex,
		LockIndex:   in.LockIndex,
		Flags:       in.Flags,
		Value:       value,
		Session:     in.Session,
	}
}

// periodic loop to wake people up, so they can honour timeouts
func (m *mockKV) loop() {
	for range time.Tick(1 * time.Second) {
		m.mtx.Lock()
		m.cond.Broadcast()
		m.mtx.Unlock()
	}
}

func (m *mockKV) CAS(p *consul.KVPair, q *consul.WriteOptions) (bool, *consul.WriteMeta, error) {
	m.mtx.Lock()
	defer m.mtx.Unlock()
	existing, ok := m.kvps[p.Key]
	if ok && existing.ModifyIndex != p.ModifyIndex {
		return false, nil, nil
	}
	if ok {
		existing.Value = p.Value
	} else {
		m.kvps[p.Key] = copyKVPair(p)
	}
	m.kvps[p.Key].ModifyIndex++
	m.kvps[p.Key].LockIndex = m.next
	m.next++
	m.cond.Broadcast()
	return true, nil, nil
}

func (m *mockKV) Get(key string, q *consul.QueryOptions) (*consul.KVPair, *consul.QueryMeta, error) {
	m.mtx.Lock()
	defer m.mtx.Unlock()
	value, ok := m.kvps[key]
	if !ok {
		return nil, nil, nil
	}
	for q.WaitIndex >= value.ModifyIndex {
		m.cond.Wait()
	}
	return copyKVPair(value), nil, nil
}

func (m *mockKV) List(prefix string, q *consul.QueryOptions) (consul.KVPairs, *consul.QueryMeta, error) {
	m.mtx.Lock()
	defer m.mtx.Unlock()
	deadline := time.Now().Add(q.WaitTime)
	for m.next <= q.WaitIndex && time.Now().Before(deadline) {
		m.cond.Wait()
	}
	if time.Now().After(deadline) {
		return nil, &consul.QueryMeta{LastIndex: q.WaitIndex}, nil
	}
	result := consul.KVPairs{}
	for _, kvp := range m.kvps {
		if kvp.LockIndex >= q.WaitIndex {
			result = append(result, copyKVPair(kvp))
		}
	}
	return result, &consul.QueryMeta{LastIndex: m.next}, nil
}
