package hh

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"testing"
	"time"
)

func BenchmarkQueueAppend(b *testing.B) {
	dir, err := os.MkdirTemp("", "hh_queue")
	if err != nil {
		b.Fatalf("failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(dir)

	q, err := newQueue(dir, 1024*1024*1024, 1024)
	if err != nil {
		b.Fatalf("failed to create queue: %v", err)
	}

	if err := q.Open(); err != nil {
		b.Fatalf("failed to open queue: %v", err)
	}

	for i := 0; i < b.N; i++ {
		if err := q.Append([]byte(fmt.Sprintf("%d", i))); err != nil {
			println(q.diskUsage())
			b.Fatalf("Queue.Append failed: %v", err)
		}
	}
}

func TestQueueAppendOne(t *testing.T) {
	dir, err := os.MkdirTemp("", "hh_queue")
	if err != nil {
		t.Fatalf("failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(dir)

	q, err := newQueue(dir, 1024, 1024)
	if err != nil {
		t.Fatalf("failed to create queue: %v", err)
	}

	if err := q.Open(); err != nil {
		t.Fatalf("failed to open queue: %v", err)
	}

	if err := q.Append([]byte("test")); err != nil {
		t.Fatalf("Queue.Append failed: %v", err)
	}

	exp := filepath.Join(dir, "1")
	stats, err := os.Stat(exp)
	if os.IsNotExist(err) {
		t.Fatalf("Queue.Append file not exists. exp %v to exist", exp)
	}

	// 8 byte header ptr + 8 byte record len + record len
	if exp := int64(8 + 8 + 4); stats.Size() != exp {
		t.Fatalf("Queue.Append file size mismatch. got %v, exp %v", stats.Size(), exp)
	}

	cur, err := q.Current()
	if err != nil {
		t.Fatalf("Queue.Current failed: %v", err)
	}

	if exp := "test"; string(cur) != exp {
		t.Errorf("Queue.Current mismatch: got %v, exp %v", string(cur), exp)
	}
}

func TestQueueAppendMultiple(t *testing.T) {
	dir, err := os.MkdirTemp("", "hh_queue")
	if err != nil {
		t.Fatalf("failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(dir)

	q, err := newQueue(dir, 1024, 1024)
	if err != nil {
		t.Fatalf("failed to create queue: %v", err)
	}

	if err := q.Open(); err != nil {
		t.Fatalf("failed to open queue: %v", err)
	}

	if err := q.Append([]byte("one")); err != nil {
		t.Fatalf("Queue.Append failed: %v", err)
	}

	if err := q.Append([]byte("two")); err != nil {
		t.Fatalf("Queue.Append failed: %v", err)
	}

	for _, exp := range []string{"one", "two"} {
		cur, err := q.Current()
		if err != nil {
			t.Fatalf("Queue.Current failed: %v", err)
		}

		if string(cur) != exp {
			t.Errorf("Queue.Current mismatch: got %v, exp %v", string(cur), exp)
		}

		if err := q.Advance(); err != nil {
			t.Fatalf("Queue.Advance failed: %v", err)
		}
	}
}

func TestQueueAdvancePastEnd(t *testing.T) {
	dir, err := os.MkdirTemp("", "hh_queue")
	if err != nil {
		t.Fatalf("failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(dir)

	// create the queue
	q, err := newQueue(dir, 1024, 1024)
	if err != nil {
		t.Fatalf("failed to create queue: %v", err)
	}

	if err := q.Open(); err != nil {
		t.Fatalf("failed to open queue: %v", err)
	}

	// append one entry, should go to the first segment
	if err := q.Append([]byte("one")); err != nil {
		t.Fatalf("Queue.Append failed: %v", err)
	}

	// set the segment size low to force a new segment to be created
	q.SetMaxSegmentSize(12)

	// Should go into a new segment
	if err := q.Append([]byte("two")); err != nil {
		t.Fatalf("Queue.Append failed: %v", err)
	}

	// should read from first segment
	cur, err := q.Current()
	if err != nil {
		t.Fatalf("Queue.Current failed: %v", err)
	}

	if exp := "one"; string(cur) != exp {
		t.Errorf("Queue.Current mismatch: got %v, exp %v", string(cur), exp)
	}

	if err := q.Advance(); err != nil {
		t.Fatalf("Queue.Advance failed: %v", err)
	}

	// ensure the first segment file is removed since we've advanced past the end
	_, err = os.Stat(filepath.Join(dir, "1"))
	if !os.IsNotExist(err) {
		t.Fatalf("Queue.Advance should have removed the segment")
	}

	// should read from second segment
	cur, err = q.Current()
	if err != nil {
		t.Fatalf("Queue.Current failed: %v", err)
	}

	if exp := "two"; string(cur) != exp {
		t.Errorf("Queue.Current mismatch: got %v, exp %v", string(cur), exp)
	}

	_, err = os.Stat(filepath.Join(dir, "2"))
	if os.IsNotExist(err) {
		t.Fatalf("Queue.Advance should have removed the segment")
	}

	if err := q.Advance(); err != nil {
		t.Fatalf("Queue.Advance failed: %v", err)
	}

	cur, err = q.Current()
	if err != io.EOF {
		t.Fatalf("Queue.Current should have returned error")
	}
}

func TestQueueFull(t *testing.T) {
	dir, err := os.MkdirTemp("", "hh_queue")
	if err != nil {
		t.Fatalf("failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(dir)

	// create the queue
	q, err := newQueue(dir, 10, 1024)
	if err != nil {
		t.Fatalf("failed to create queue: %v", err)
	}

	if err := q.Open(); err != nil {
		t.Fatalf("failed to open queue: %v", err)
	}

	if err := q.Append([]byte("one")); err != ErrQueueFull {
		t.Fatalf("Queue.Append expected to return queue full")
	}
}

func TestQueueReopen(t *testing.T) {
	dir, err := os.MkdirTemp("", "hh_queue")
	if err != nil {
		t.Fatalf("failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(dir)

	// create the queue
	q, err := newQueue(dir, 1024, 1024)
	if err != nil {
		t.Fatalf("failed to create queue: %v", err)
	}

	if err := q.Open(); err != nil {
		t.Fatalf("failed to open queue: %v", err)
	}

	if err := q.Append([]byte("one")); err != nil {
		t.Fatalf("Queue.Append failed: %v", err)
	}

	cur, err := q.Current()
	if err != nil {
		t.Fatalf("Queue.Current failed: %v", err)
	}

	if exp := "one"; string(cur) != exp {
		t.Errorf("Queue.Current mismatch: got %v, exp %v", string(cur), exp)
	}

	// close and re-open the queue
	if err := q.Close(); err != nil {
		t.Fatalf("Queue.Close failed: %v", err)
	}

	if err := q.Open(); err != nil {
		t.Fatalf("failed to re-open queue: %v", err)
	}

	// Make sure we can read back the last current value
	cur, err = q.Current()
	if err != nil {
		t.Fatalf("Queue.Current failed: %v", err)
	}

	if exp := "one"; string(cur) != exp {
		t.Errorf("Queue.Current mismatch: got %v, exp %v", string(cur), exp)
	}

	if err := q.Append([]byte("two")); err != nil {
		t.Fatalf("Queue.Append failed: %v", err)
	}

	if err := q.Advance(); err != nil {
		t.Fatalf("Queue.Advance failed: %v", err)
	}

	cur, err = q.Current()
	if err != nil {
		t.Fatalf("Queue.Current failed: %v", err)
	}

	if exp := "two"; string(cur) != exp {
		t.Errorf("Queue.Current mismatch: got %v, exp %v", string(cur), exp)
	}
}

func TestPurgeQueue(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping purge queue")
	}

	dir, err := os.MkdirTemp("", "hh_queue")
	if err != nil {
		t.Fatalf("failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(dir)

	// create the queue
	q, err := newQueue(dir, 1024, 1024)
	if err != nil {
		t.Fatalf("failed to create queue: %v", err)
	}

	if err := q.Open(); err != nil {
		t.Fatalf("failed to open queue: %v", err)
	}

	if err := q.Append([]byte("one")); err != nil {
		t.Fatalf("Queue.Append failed: %v", err)
	}

	cur, err := q.Current()
	if err != nil {
		t.Fatalf("Queue.Current failed: %v", err)
	}

	if exp := "one"; string(cur) != exp {
		t.Errorf("Queue.Current mismatch: got %v, exp %v", string(cur), exp)
	}

	time.Sleep(time.Second)

	if err := q.PurgeOlderThan(time.Now()); err != nil {
		t.Errorf("Queue.PurgeOlderThan failed: %v", err)
	}

	_, err = q.Current()
	if err != io.EOF {
		t.Fatalf("Queue.Current expected io.EOF, got: %v", err)
	}

}
