// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package raft

import (
	"bytes"
	"io"
	"reflect"
	"testing"
)

func TestInmemSnapshotStoreImpl(t *testing.T) {
	var impl interface{} = &InmemSnapshotStore{}
	if _, ok := impl.(SnapshotStore); !ok {
		t.Fatalf("InmemSnapshotStore not a SnapshotStore")
	}
}

func TestInmemSnapshotSinkImpl(t *testing.T) {
	var impl interface{} = &InmemSnapshotSink{}
	if _, ok := impl.(SnapshotSink); !ok {
		t.Fatalf("InmemSnapshotSink not a SnapshotSink")
	}
}

func TestInmemSS_CreateSnapshot(t *testing.T) {
	snap := NewInmemSnapshotStore()

	// Check no snapshots
	snaps, err := snap.List()
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if len(snaps) != 0 {
		t.Fatalf("did not expect any snapshots: %v", snaps)
	}

	// Create a new sink
	var configuration Configuration
	configuration.Servers = append(configuration.Servers, Server{
		Suffrage: Voter,
		ID:       ServerID("my id"),
		Address:  ServerAddress("over here"),
	})
	_, trans := NewInmemTransport(NewInmemAddr())
	sink, err := snap.Create(SnapshotVersionMax, 10, 3, configuration, 2, trans)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// The sink is not done, should not be in a list!
	snaps, err = snap.List()
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if len(snaps) != 1 {
		t.Fatalf("should always be 1 snapshot: %v", snaps)
	}

	// Write to the sink
	_, err = sink.Write([]byte("first\n"))
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	_, err = sink.Write([]byte("second\n"))
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Done!
	err = sink.Close()
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Should have a snapshot!
	snaps, err = snap.List()
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if len(snaps) != 1 {
		t.Fatalf("expect a snapshots: %v", snaps)
	}

	// Check the latest
	latest := snaps[0]
	if latest.Index != 10 {
		t.Fatalf("bad snapshot: %v", *latest)
	}
	if latest.Term != 3 {
		t.Fatalf("bad snapshot: %v", *latest)
	}
	if !reflect.DeepEqual(latest.Configuration, configuration) {
		t.Fatalf("bad snapshot: %v", *latest)
	}
	if latest.ConfigurationIndex != 2 {
		t.Fatalf("bad snapshot: %v", *latest)
	}
	if latest.Size != 13 {
		t.Fatalf("bad snapshot: %v", *latest)
	}

	// Read the snapshot
	_, r, err := snap.Open(latest.ID)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Read out everything
	var buf bytes.Buffer
	if _, err := io.Copy(&buf, r); err != nil {
		t.Fatalf("err: %v", err)
	}
	if err := r.Close(); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Ensure a match
	if bytes.Compare(buf.Bytes(), []byte("first\nsecond\n")) != 0 {
		t.Fatalf("content mismatch")
	}
}

func TestInmemSS_OpenSnapshotTwice(t *testing.T) {
	snap := NewInmemSnapshotStore()

	// Create a new sink
	var configuration Configuration
	configuration.Servers = append(configuration.Servers, Server{
		Suffrage: Voter,
		ID:       ServerID("my id"),
		Address:  ServerAddress("over here"),
	})
	_, trans := NewInmemTransport(NewInmemAddr())
	sink, err := snap.Create(SnapshotVersionMax, 10, 3, configuration, 2, trans)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Write to the sink
	_, err = sink.Write([]byte("data\n"))
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	err = sink.Close()
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Read the snapshot a first time
	_, r, err := snap.Open(sink.ID())
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Read out everything
	var buf1 bytes.Buffer
	if _, err = io.Copy(&buf1, r); err != nil {
		t.Fatalf("err: %v", err)
	}
	if err = r.Close(); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Ensure a match
	if bytes.Compare(buf1.Bytes(), []byte("data\n")) != 0 {
		t.Fatalf("content mismatch")
	}

	// Read the snapshot a second time.
	_, r, err = snap.Open(sink.ID())
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Read out everything again
	var buf2 bytes.Buffer
	if _, err := io.Copy(&buf2, r); err != nil {
		t.Fatalf("err: %v", err)
	}
	if err := r.Close(); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Ensure it's still the same content
	if bytes.Compare(buf2.Bytes(), []byte("data\n")) != 0 {
		t.Fatalf("content mismatch")
	}
}
