package tsm1_test

import (
	"io"
	"os"
	"reflect"
	"testing"

	"github.com/influxdata/influxdb/v2/tsdb/engine/tsm1"
)

// Test that an error is returned if a manifest isn't the first thing written
// to a digest.
func TestEngine_DigestManifestNotWritten(t *testing.T) {
	f := MustTempFile(t, "")
	w, err := tsm1.NewDigestWriter(f)
	if err != nil {
		t.Fatalf("NewDigestWriter: %v", err)
	}
	defer w.Close()

	ts := &tsm1.DigestTimeSpan{}
	ts.Add(1, 2, 3, 4)

	if err := w.WriteTimeSpan("cpu", ts); err != tsm1.ErrNoDigestManifest {
		t.Fatalf("exp: tsm1.ErrNoDigestManifest, got: %v", err)
	}
}

// Test that a digest reader will skip over the manifest without error
// if needed.
func TestEngine_DigestReadSkipsManifest(t *testing.T) {
	f := MustTempFile(t, "")
	w, err := tsm1.NewDigestWriter(f)
	if err != nil {
		t.Fatalf("NewDigestWriter: %v", err)
	}

	// Write an empty manifest.
	if err := w.WriteManifest(&tsm1.DigestManifest{}); err != nil {
		t.Fatal(err)
	}

	// Write a time span.
	ts := &tsm1.DigestTimeSpan{}
	ts.Add(1, 2, 3, 4)

	if err := w.WriteTimeSpan("cpu", ts); err != nil {
		t.Fatal(err)
	}

	if err := w.Close(); err != nil {
		t.Fatal(err)
	}

	// Open the digest and create a reader.
	f, err = os.Open(f.Name())
	if err != nil {
		t.Fatalf("Open: %v", err)
	}

	r, err := tsm1.NewDigestReader(f)
	if err != nil {
		t.Fatalf("NewDigestReader: %v", err)
	}

	// Test that we can read the timespan without first reading the manifest.
	key, ts, err := r.ReadTimeSpan()
	if err != nil {
		t.Fatal(err)
	} else if key != "cpu" {
		t.Fatalf("exp: cpu, got: %s", key)
	} else if len(ts.Ranges) != 1 {
		t.Fatalf("exp: 1, got: %d", len(ts.Ranges))
	} else if ts.Ranges[0].Min != 1 {
		t.Fatalf("exp: 1, got: %d", ts.Ranges[0].Min)
	} else if ts.Ranges[0].Max != 2 {
		t.Fatalf("exp: 1, got: %d", ts.Ranges[0].Min)
	} else if ts.Ranges[0].N != 3 {
		t.Fatalf("exp: 1, got: %d", ts.Ranges[0].N)
	} else if ts.Ranges[0].CRC != 4 {
		t.Fatalf("exp: 1, got: %d", ts.Ranges[0].CRC)
	}
}

// Test that we get an error if a digest manifest is written twice.
func TestEngine_DigestManifestDoubleWrite(t *testing.T) {
	f := MustTempFile(t, "")
	w, err := tsm1.NewDigestWriter(f)
	if err != nil {
		t.Fatalf("NewDigestWriter: %v", err)
	}
	defer w.Close()

	if err := w.WriteManifest(&tsm1.DigestManifest{}); err != nil {
		t.Fatal(err)
	}

	if err := w.WriteManifest(&tsm1.DigestManifest{}); err != tsm1.ErrDigestAlreadyWritten {
		t.Fatalf("exp: %s, got: %s", tsm1.ErrDigestAlreadyWritten, err)
	}
}

// Test that we get an error if the manifest is read twice.
func TestEngine_DigestManifestDoubleRead(t *testing.T) {
	f := MustTempFile(t, "")
	w, err := tsm1.NewDigestWriter(f)
	if err != nil {
		t.Fatalf("NewDigestWriter: %v", err)
	}

	// Write the manifest.
	if err := w.WriteManifest(&tsm1.DigestManifest{Dir: "test"}); err != nil {
		t.Fatal(err)
	}
	if err := w.Close(); err != nil {
		t.Fatal(err)
	}

	// Open the digest and create a reader.
	f, err = os.Open(f.Name())
	if err != nil {
		t.Fatalf("Open: %v", err)
	}

	r, err := tsm1.NewDigestReader(f)
	if err != nil {
		t.Fatalf("NewDigestReader: %v", err)
	}

	// Read the manifest.
	if m, err := r.ReadManifest(); err != nil {
		t.Fatal(err)
	} else if m.Dir != "test" {
		t.Fatalf("exp: test, got: %s", m.Dir)
	}

	// Attempt to read the manifest a second time (should fail).
	if _, err := r.ReadManifest(); err != tsm1.ErrDigestManifestAlreadyRead {
		t.Fatalf("exp: digest manifest already read, got: %v", err)
	}
}

// Test writing and reading a digest.
func TestEngine_DigestWriterReader(t *testing.T) {
	f := MustTempFile(t, "")
	w, err := tsm1.NewDigestWriter(f)
	if err != nil {
		t.Fatalf("NewDigestWriter: %v", err)
	}

	if err := w.WriteManifest(&tsm1.DigestManifest{}); err != nil {
		t.Fatal(err)
	}

	ts := &tsm1.DigestTimeSpan{}
	ts.Add(1, 2, 3, 4)

	if err := w.WriteTimeSpan("cpu", ts); err != nil {
		t.Fatalf("WriteTimeSpan: %v", err)
	}

	if err := w.Close(); err != nil {
		t.Fatalf("Close: %v", err)
	}

	f, err = os.Open(f.Name())
	if err != nil {
		t.Fatalf("Open: %v", err)
	}

	r, err := tsm1.NewDigestReader(f)
	if err != nil {
		t.Fatalf("NewDigestReader: %v", err)
	}
	for {

		key, ts, err := r.ReadTimeSpan()
		if err == io.EOF {
			break
		} else if err != nil {
			t.Fatalf("ReadTimeSpan: %v", err)
		}

		if exp, got := "cpu", key; exp != got {
			t.Fatalf("key mismatch: exp %v, got %v", exp, got)
		}

		if exp, got := 1, len(ts.Ranges); exp != got {
			t.Fatalf("range len mismatch: exp %v, got %v", exp, got)
		}

		exp := tsm1.DigestTimeRange{Min: 1, Max: 2, N: 3, CRC: 4}
		if got := ts.Ranges[0]; !reflect.DeepEqual(exp, got) {
			t.Fatalf("time range mismatch: exp %v, got %v", exp, got)
		}
	}
}
