// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package mockstore

import (
	"time"

	"go.etcd.io/etcd/client/pkg/v3/testutil"
	"go.etcd.io/etcd/server/v3/etcdserver/api/v2store"
)

// StoreRecorder provides a Store interface with a testutil.Recorder
type StoreRecorder struct {
	v2store.Store
	testutil.Recorder
}

// storeRecorder records all the methods it receives.
// storeRecorder DOES NOT work as a actual v2store.
// It always returns invalid empty response and no error.
type storeRecorder struct {
	v2store.Store
	testutil.Recorder
}

func NewNop() v2store.Store { return &storeRecorder{Recorder: &testutil.RecorderBuffered{}} }
func NewRecorder() *StoreRecorder {
	sr := &storeRecorder{Recorder: &testutil.RecorderBuffered{}}
	return &StoreRecorder{Store: sr, Recorder: sr.Recorder}
}

func NewRecorderStream() *StoreRecorder {
	sr := &storeRecorder{Recorder: testutil.NewRecorderStream()}
	return &StoreRecorder{Store: sr, Recorder: sr.Recorder}
}

func (s *storeRecorder) Version() int  { return 0 }
func (s *storeRecorder) Index() uint64 { return 0 }
func (s *storeRecorder) Get(path string, recursive, sorted bool) (*v2store.Event, error) {
	s.Record(testutil.Action{
		Name:   "Get",
		Params: []any{path, recursive, sorted},
	})
	return &v2store.Event{}, nil
}

func (s *storeRecorder) Set(path string, dir bool, val string, expireOpts v2store.TTLOptionSet) (*v2store.Event, error) {
	s.Record(testutil.Action{
		Name:   "Set",
		Params: []any{path, dir, val, expireOpts},
	})
	return &v2store.Event{}, nil
}

func (s *storeRecorder) Update(path, val string, expireOpts v2store.TTLOptionSet) (*v2store.Event, error) {
	s.Record(testutil.Action{
		Name:   "Update",
		Params: []any{path, val, expireOpts},
	})
	return &v2store.Event{}, nil
}

func (s *storeRecorder) Create(path string, dir bool, val string, uniq bool, expireOpts v2store.TTLOptionSet) (*v2store.Event, error) {
	s.Record(testutil.Action{
		Name:   "Create",
		Params: []any{path, dir, val, uniq, expireOpts},
	})
	return &v2store.Event{}, nil
}

func (s *storeRecorder) CompareAndSwap(path, prevVal string, prevIdx uint64, val string, expireOpts v2store.TTLOptionSet) (*v2store.Event, error) {
	s.Record(testutil.Action{
		Name:   "CompareAndSwap",
		Params: []any{path, prevVal, prevIdx, val, expireOpts},
	})
	return &v2store.Event{}, nil
}

func (s *storeRecorder) Delete(path string, dir, recursive bool) (*v2store.Event, error) {
	s.Record(testutil.Action{
		Name:   "Delete",
		Params: []any{path, dir, recursive},
	})
	return &v2store.Event{}, nil
}

func (s *storeRecorder) CompareAndDelete(path, prevVal string, prevIdx uint64) (*v2store.Event, error) {
	s.Record(testutil.Action{
		Name:   "CompareAndDelete",
		Params: []any{path, prevVal, prevIdx},
	})
	return &v2store.Event{}, nil
}

func (s *storeRecorder) Watch(_ string, _, _ bool, _ uint64) (v2store.Watcher, error) {
	s.Record(testutil.Action{Name: "Watch"})
	return v2store.NewNopWatcher(), nil
}

func (s *storeRecorder) Save() ([]byte, error) {
	s.Record(testutil.Action{Name: "Save"})
	return nil, nil
}

func (s *storeRecorder) Recovery(b []byte) error {
	s.Record(testutil.Action{Name: "Recovery"})
	return nil
}

func (s *storeRecorder) SaveNoCopy() ([]byte, error) {
	s.Record(testutil.Action{Name: "SaveNoCopy"})
	return nil, nil
}

func (s *storeRecorder) Clone() v2store.Store {
	s.Record(testutil.Action{Name: "Clone"})
	return s
}

//revive:disable:var-naming
func (s *storeRecorder) JsonStats() []byte { return nil }

//revive:enable:var-naming

func (s *storeRecorder) DeleteExpiredKeys(cutoff time.Time) {
	s.Record(testutil.Action{
		Name:   "DeleteExpiredKeys",
		Params: []any{cutoff},
	})
}

func (s *storeRecorder) HasTTLKeys() bool {
	s.Record(testutil.Action{
		Name: "HasTTLKeys",
	})
	return true
}

// errStoreRecorder is a storeRecorder, but returns the given error on
// Get, Watch methods.
type errStoreRecorder struct {
	storeRecorder
	err error
}

func NewErrRecorder(err error) *StoreRecorder {
	sr := &errStoreRecorder{err: err}
	sr.Recorder = &testutil.RecorderBuffered{}
	return &StoreRecorder{Store: sr, Recorder: sr.Recorder}
}

func (s *errStoreRecorder) Get(path string, recursive, sorted bool) (*v2store.Event, error) {
	s.storeRecorder.Get(path, recursive, sorted)
	return nil, s.err
}

func (s *errStoreRecorder) Watch(path string, recursive, sorted bool, index uint64) (v2store.Watcher, error) {
	s.storeRecorder.Watch(path, recursive, sorted, index)
	return nil, s.err
}
