// Copyright 2019 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 confchange

import (
	"fmt"
	"math/rand"
	"reflect"
	"testing"
	"testing/quick"

	pb "go.etcd.io/etcd/raft/v3/raftpb"
	"go.etcd.io/etcd/raft/v3/tracker"
)

// TestConfChangeQuick uses quickcheck to verify that simple and joint config
// changes arrive at the same result.
func TestConfChangeQuick(t *testing.T) {
	cfg := &quick.Config{
		MaxCount: 1000,
	}

	// Log the first couple of runs to give some indication of things working
	// as intended.
	const infoCount = 5

	runWithJoint := func(c *Changer, ccs []pb.ConfChangeSingle) error {
		cfg, prs, err := c.EnterJoint(false /* autoLeave */, ccs...)
		if err != nil {
			return err
		}
		// Also do this with autoLeave on, just to check that we'd get the same
		// result.
		cfg2a, prs2a, err := c.EnterJoint(true /* autoLeave */, ccs...)
		if err != nil {
			return err
		}
		cfg2a.AutoLeave = false
		if !reflect.DeepEqual(cfg, cfg2a) || !reflect.DeepEqual(prs, prs2a) {
			return fmt.Errorf("cfg: %+v\ncfg2a: %+v\nprs: %+v\nprs2a: %+v",
				cfg, cfg2a, prs, prs2a)
		}
		c.Tracker.Config = cfg
		c.Tracker.Progress = prs
		cfg2b, prs2b, err := c.LeaveJoint()
		if err != nil {
			return err
		}
		// Reset back to the main branch with autoLeave=false.
		c.Tracker.Config = cfg
		c.Tracker.Progress = prs
		cfg, prs, err = c.LeaveJoint()
		if err != nil {
			return err
		}
		if !reflect.DeepEqual(cfg, cfg2b) || !reflect.DeepEqual(prs, prs2b) {
			return fmt.Errorf("cfg: %+v\ncfg2b: %+v\nprs: %+v\nprs2b: %+v",
				cfg, cfg2b, prs, prs2b)
		}
		c.Tracker.Config = cfg
		c.Tracker.Progress = prs
		return nil
	}

	runWithSimple := func(c *Changer, ccs []pb.ConfChangeSingle) error {
		for _, cc := range ccs {
			cfg, prs, err := c.Simple(cc)
			if err != nil {
				return err
			}
			c.Tracker.Config, c.Tracker.Progress = cfg, prs
		}
		return nil
	}

	type testFunc func(*Changer, []pb.ConfChangeSingle) error

	wrapper := func(invoke testFunc) func(setup initialChanges, ccs confChanges) (*Changer, error) {
		return func(setup initialChanges, ccs confChanges) (*Changer, error) {
			tr := tracker.MakeProgressTracker(10)
			c := &Changer{
				Tracker:   tr,
				LastIndex: 10,
			}

			if err := runWithSimple(c, setup); err != nil {
				return nil, err
			}

			err := invoke(c, ccs)
			return c, err
		}
	}

	var n int
	f1 := func(setup initialChanges, ccs confChanges) *Changer {
		c, err := wrapper(runWithSimple)(setup, ccs)
		if err != nil {
			t.Fatal(err)
		}
		if n < infoCount {
			t.Log("initial setup:", Describe(setup...))
			t.Log("changes:", Describe(ccs...))
			t.Log(c.Tracker.Config)
			t.Log(c.Tracker.Progress)
		}
		n++
		return c
	}
	f2 := func(setup initialChanges, ccs confChanges) *Changer {
		c, err := wrapper(runWithJoint)(setup, ccs)
		if err != nil {
			t.Fatal(err)
		}
		return c
	}
	err := quick.CheckEqual(f1, f2, cfg)
	if err == nil {
		return
	}
	cErr, ok := err.(*quick.CheckEqualError)
	if !ok {
		t.Fatal(err)
	}

	t.Error("setup:", Describe(cErr.In[0].([]pb.ConfChangeSingle)...))
	t.Error("ccs:", Describe(cErr.In[1].([]pb.ConfChangeSingle)...))
	t.Errorf("out1: %+v\nout2: %+v", cErr.Out1, cErr.Out2)
}

type confChangeTyp pb.ConfChangeType

func (confChangeTyp) Generate(rand *rand.Rand, _ int) reflect.Value {
	return reflect.ValueOf(confChangeTyp(rand.Intn(4)))
}

type confChanges []pb.ConfChangeSingle

func genCC(num func() int, id func() uint64, typ func() pb.ConfChangeType) []pb.ConfChangeSingle {
	var ccs []pb.ConfChangeSingle
	n := num()
	for i := 0; i < n; i++ {
		ccs = append(ccs, pb.ConfChangeSingle{Type: typ(), NodeID: id()})
	}
	return ccs
}

func (confChanges) Generate(rand *rand.Rand, _ int) reflect.Value {
	num := func() int {
		return 1 + rand.Intn(9)
	}
	id := func() uint64 {
		// Note that num() >= 1, so we're never returning 1 from this method,
		// meaning that we'll never touch NodeID one, which is special to avoid
		// voterless configs altogether in this test.
		return 1 + uint64(num())
	}
	typ := func() pb.ConfChangeType {
		return pb.ConfChangeType(rand.Intn(len(pb.ConfChangeType_name)))
	}
	return reflect.ValueOf(genCC(num, id, typ))
}

type initialChanges []pb.ConfChangeSingle

func (initialChanges) Generate(rand *rand.Rand, _ int) reflect.Value {
	num := func() int {
		return 1 + rand.Intn(5)
	}
	id := func() uint64 { return uint64(num()) }
	typ := func() pb.ConfChangeType {
		return pb.ConfChangeAddNode
	}
	// NodeID one is special - it's in the initial config and will be a voter
	// always (this is to avoid uninteresting edge cases where the simple conf
	// changes can't easily make progress).
	ccs := append([]pb.ConfChangeSingle{{Type: pb.ConfChangeAddNode, NodeID: 1}}, genCC(num, id, typ)...)
	return reflect.ValueOf(ccs)
}
