// 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 rafthttp

import (
	"bytes"
	"encoding/binary"
	"io"
	"net/http"
	"reflect"
	"testing"

	"go.etcd.io/etcd/api/v3/version"
	"go.etcd.io/etcd/raft/v3/raftpb"

	"github.com/coreos/go-semver/semver"
)

func TestEntry(t *testing.T) {
	tests := []raftpb.Entry{
		{},
		{Term: 1, Index: 1},
		{Term: 1, Index: 1, Data: []byte("some data")},
	}
	for i, tt := range tests {
		b := &bytes.Buffer{}
		if err := writeEntryTo(b, &tt); err != nil {
			t.Errorf("#%d: unexpected write ents error: %v", i, err)
			continue
		}
		var ent raftpb.Entry
		if err := readEntryFrom(b, &ent); err != nil {
			t.Errorf("#%d: unexpected read ents error: %v", i, err)
			continue
		}
		if !reflect.DeepEqual(ent, tt) {
			t.Errorf("#%d: ent = %+v, want %+v", i, ent, tt)
		}
	}
}

func TestCompareMajorMinorVersion(t *testing.T) {
	tests := []struct {
		va, vb *semver.Version
		w      int
	}{
		// equal to
		{
			semver.Must(semver.NewVersion("2.1.0")),
			semver.Must(semver.NewVersion("2.1.0")),
			0,
		},
		// smaller than
		{
			semver.Must(semver.NewVersion("2.0.0")),
			semver.Must(semver.NewVersion("2.1.0")),
			-1,
		},
		// bigger than
		{
			semver.Must(semver.NewVersion("2.2.0")),
			semver.Must(semver.NewVersion("2.1.0")),
			1,
		},
		// ignore patch
		{
			semver.Must(semver.NewVersion("2.1.1")),
			semver.Must(semver.NewVersion("2.1.0")),
			0,
		},
		// ignore prerelease
		{
			semver.Must(semver.NewVersion("2.1.0-alpha.0")),
			semver.Must(semver.NewVersion("2.1.0")),
			0,
		},
	}
	for i, tt := range tests {
		if g := compareMajorMinorVersion(tt.va, tt.vb); g != tt.w {
			t.Errorf("#%d: compare = %d, want %d", i, g, tt.w)
		}
	}
}

func TestServerVersion(t *testing.T) {
	tests := []struct {
		h  http.Header
		wv *semver.Version
	}{
		// backward compatibility with etcd 2.0
		{
			http.Header{},
			semver.Must(semver.NewVersion("2.0.0")),
		},
		{
			http.Header{"X-Server-Version": []string{"2.1.0"}},
			semver.Must(semver.NewVersion("2.1.0")),
		},
		{
			http.Header{"X-Server-Version": []string{"2.1.0-alpha.0+git"}},
			semver.Must(semver.NewVersion("2.1.0-alpha.0+git")),
		},
	}
	for i, tt := range tests {
		v := serverVersion(tt.h)
		if v.String() != tt.wv.String() {
			t.Errorf("#%d: version = %s, want %s", i, v, tt.wv)
		}
	}
}

func TestMinClusterVersion(t *testing.T) {
	tests := []struct {
		h  http.Header
		wv *semver.Version
	}{
		// backward compatibility with etcd 2.0
		{
			http.Header{},
			semver.Must(semver.NewVersion("2.0.0")),
		},
		{
			http.Header{"X-Min-Cluster-Version": []string{"2.1.0"}},
			semver.Must(semver.NewVersion("2.1.0")),
		},
		{
			http.Header{"X-Min-Cluster-Version": []string{"2.1.0-alpha.0+git"}},
			semver.Must(semver.NewVersion("2.1.0-alpha.0+git")),
		},
	}
	for i, tt := range tests {
		v := minClusterVersion(tt.h)
		if v.String() != tt.wv.String() {
			t.Errorf("#%d: version = %s, want %s", i, v, tt.wv)
		}
	}
}

func TestCheckVersionCompatibility(t *testing.T) {
	ls := semver.Must(semver.NewVersion(version.Version))
	lmc := semver.Must(semver.NewVersion(version.MinClusterVersion))
	tests := []struct {
		server     *semver.Version
		minCluster *semver.Version
		wok        bool
	}{
		// the same version as local
		{
			ls,
			lmc,
			true,
		},
		// one version lower
		{
			lmc,
			&semver.Version{},
			true,
		},
		// one version higher
		{
			&semver.Version{Major: ls.Major + 1},
			ls,
			true,
		},
		// too low version
		{
			&semver.Version{Major: lmc.Major - 1},
			&semver.Version{},
			false,
		},
		// too high version
		{
			&semver.Version{Major: ls.Major + 1, Minor: 1},
			&semver.Version{Major: ls.Major + 1},
			false,
		},
	}
	for i, tt := range tests {
		_, _, err := checkVersionCompatibility("", tt.server, tt.minCluster)
		if ok := err == nil; ok != tt.wok {
			t.Errorf("#%d: ok = %v, want %v", i, ok, tt.wok)
		}
	}
}

func writeEntryTo(w io.Writer, ent *raftpb.Entry) error {
	size := ent.Size()
	if err := binary.Write(w, binary.BigEndian, uint64(size)); err != nil {
		return err
	}
	b, err := ent.Marshal()
	if err != nil {
		return err
	}
	_, err = w.Write(b)
	return err
}

func readEntryFrom(r io.Reader, ent *raftpb.Entry) error {
	var l uint64
	if err := binary.Read(r, binary.BigEndian, &l); err != nil {
		return err
	}
	buf := make([]byte, int(l))
	if _, err := io.ReadFull(r, buf); err != nil {
		return err
	}
	return ent.Unmarshal(buf)
}
