package loglevel

import (
	"io"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"testing"
)

func createFileRotater(t *testing.T) (*FileRotater, func()) {
	d, err := os.MkdirTemp("", "logtest")
	if err != nil {
		t.Fatalf("failed creating tmp dir: %v", err)
	}
	fn := filepath.Join(d, "test.log")
	fr, err := NewFileRotater(fn)
	if err != nil {
		t.Fatalf("NewFileRotater error: %v", err)
	}
	if fr == nil {
		t.Fatal("NewFileRotater returned nil")
	}
	return fr, func() {
		err := os.RemoveAll(d)
		if err != nil {
			t.Errorf("failed removing file: %v", err)
		}
	}
}

func TestNewFileRotater(t *testing.T) {
	_, cleanup := createFileRotater(t)
	cleanup()

	d, err := os.MkdirTemp("", "anotherlogtest")
	if err != nil {
		t.Fatalf("failed creating another tmp dir: %v", err)
	}
	nef := filepath.Join(d, "notexist", "test.log")
	_, err = NewFileRotater(nef)
	if err == nil {
		t.Error("NewFileRotater returned no error with nonexistent dir")
	}
}

func TestWriteConcurrent(t *testing.T) {
	fr, cleanup := createFileRotater(t)
	defer cleanup()
	MaxSize = 5
	var wg sync.WaitGroup
	for range 5 {
		wg.Add(1)
		go func() {
			_, err := fr.Write([]byte("test"))
			defer wg.Done()
			if err != nil {
				t.Errorf("concurrent write returned an error: %v", err)
			}
		}()
	}
	wg.Wait()
}

func TestWriteTrim(t *testing.T) {
	fr, cleanup := createFileRotater(t)
	defer cleanup()
	writeNCheckSize := func(n int, size int64) {
		buf := make([]byte, n)

		for i := range n {
			buf[i] = 'x'
		}
		_, err := fr.Write(buf)
		if err != nil {
			t.Fatalf("failed writing: %v", err)
		}

		fs, err := fr.file.Stat()
		if err != nil {
			t.Fatalf("failed getting size: %v", err)
		}

		gsize := fs.Size()

		if gsize != size {
			t.Fatalf("got: %v, want: %v, max size: %v", gsize, size, MaxSize)
		}
	}

	// we test by writing a start message and checking if it disappears after trimmign
	// the max size we set to the length of the start message + 20 bytes
	begS := "this is the start"
	begB := []byte(begS)
	startN := int64(len(begB))
	MaxSize = startN + 20
	TrimSize = MaxSize / 2

	// no trim yet
	_, err := fr.Write(begB)
	if err != nil {
		t.Fatalf("failed writing start message: %v", err)
	}

	// write until the trimming size
	writeNCheckSize(5, startN+5)
	writeNCheckSize(15, MaxSize)

	// set the length we want to write
	var n int64 = 11

	// now the size should be the length of the trimmed message plus the remaining (non-trimmed part of the file) plus the length we want to write

	size := int64(len(TrimMsg)) + (MaxSize - TrimSize) + n
	writeNCheckSize(11, size)

	// disable trimming by setting it to a high value
	MaxSize = 9000
	TrimSize = 9000

	// now the size should be the old size plus the write size
	newN := 12
	writeNCheckSize(newN, size+int64(newN))

	_, err = fr.file.Seek(0, io.SeekStart)
	if err != nil {
		t.Fatalf("failed going to beginning of file: %v", err)
	}

	b, err := io.ReadAll(fr.file)
	if err != nil {
		t.Fatalf("failed reading file: %v", err)
	}

	corpus := string(b)
	if strings.Contains(corpus, begS) {
		t.Fatalf("file still contains beginning message: %v", corpus)
	}

	if !strings.Contains(corpus, TrimMsg) {
		t.Fatalf("file does not contain trim message: %v", corpus)
	}
}
