package chunker_test

import (
	"compress/gzip"
	"io"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"workshop.perforce.com/p4lf/internal/chunker"
	"workshop.perforce.com/p4lf/internal/config"
	"workshop.perforce.com/p4lf/internal/tailer"
)

type nopLogger struct{}

func (nopLogger) Infof(string, ...interface{})  {}
func (nopLogger) Warnf(string, ...interface{})  {}
func (nopLogger) Debugf(string, ...interface{}) {}
func (nopLogger) Errorf(string, ...interface{}) {}

func testConfig(t *testing.T) config.Config {
	t.Helper()
	dir := t.TempDir()
	cfg := config.Defaults()
	cfg.LogChunksDir = dir
	cfg.P4LogFile = "/dev/null"
	cfg.StateFile = filepath.Join(dir, "p4lf.state")
	return cfg
}

func TestWrite_CreatesGzipFile(t *testing.T) {
	cfg := testConfig(t)
	c, err := chunker.New(cfg, nopLogger{})
	if err != nil {
		t.Fatalf("New: %v", err)
	}

	chunk := &tailer.Chunk{
		Data:        []byte("line1\nline2\nline3\n"),
		StartOffset: 0,
		EndOffset:   18,
	}

	ok, err := c.Write(chunk)
	if err != nil {
		t.Fatalf("Write: %v", err)
	}
	if !ok {
		t.Fatal("Write returned false (skipped), expected true")
	}

	// Filename now includes a date: log.<YYYY-MM-DD>.0.18.gz
	matches, err := filepath.Glob(filepath.Join(cfg.LogChunksDir, "log.*.0.18.gz"))
	if err != nil {
		t.Fatalf("Glob error: %v", err)
	}
	if len(matches) == 0 {
		t.Fatalf("expected chunk file log.*.0.18.gz not found in %s", cfg.LogChunksDir)
	}

	// Verify the gzip content.
	f, err := os.Open(matches[0])
	if err != nil {
		t.Fatalf("opening chunk: %v", err)
	}
	defer f.Close()
	gz, err := gzip.NewReader(f)
	if err != nil {
		t.Fatalf("gzip.NewReader: %v", err)
	}
	content, err := io.ReadAll(gz)
	if err != nil {
		t.Fatalf("reading gzip content: %v", err)
	}
	if string(content) != string(chunk.Data) {
		t.Errorf("chunk content: got %q, want %q", content, chunk.Data)
	}
}

func TestWrite_MaxLogChunks(t *testing.T) {
	cfg := testConfig(t)
	cfg.MaxLogChunks = 2
	c, err := chunker.New(cfg, nopLogger{})
	if err != nil {
		t.Fatalf("New: %v", err)
	}

	// Write 3 chunks with MaxLogChunks=2; the third write should delete the
	// oldest file to stay within the limit.
	chunks := []*tailer.Chunk{
		{Data: []byte("aaa"), StartOffset: 0, EndOffset: 3},
		{Data: []byte("bbb"), StartOffset: 3, EndOffset: 6},
		{Data: []byte("ccc"), StartOffset: 6, EndOffset: 9},
	}
	for i, chunk := range chunks {
		ok, err := c.Write(chunk)
		if err != nil {
			t.Fatalf("Write[%d]: err=%v", i, err)
		}
		if !ok {
			t.Fatalf("Write[%d]: expected write to succeed (delete-oldest, not skip)", i)
		}
	}

	// After 3 writes with MaxLogChunks=2, we should have exactly 2 files.
	matches, err := filepath.Glob(filepath.Join(cfg.LogChunksDir, "log.*.gz"))
	if err != nil {
		t.Fatalf("Glob: %v", err)
	}
	if len(matches) != 2 {
		t.Errorf("expected 2 chunk files after enforcing MaxLogChunks=2, got %d", len(matches))
	}
}

func TestWrite_NoPanicOnEmptyDir(t *testing.T) {
	cfg := testConfig(t)
	cfg.MaxLogChunks = 5
	c, err := chunker.New(cfg, nopLogger{})
	if err != nil {
		t.Fatalf("New: %v", err)
	}
	chunk := &tailer.Chunk{Data: []byte("x"), StartOffset: 100, EndOffset: 101}
	ok, err := c.Write(chunk)
	if err != nil {
		t.Fatalf("Write: %v", err)
	}
	if !ok {
		t.Error("expected write to succeed")
	}
}

func TestWrite_ContentIsCorrect(t *testing.T) {
	cfg := testConfig(t)
	c, err := chunker.New(cfg, nopLogger{})
	if err != nil {
		t.Fatalf("New: %v", err)
	}

	payload := strings.Repeat("Perforce log line\n", 100)
	chunk := &tailer.Chunk{
		Data:        []byte(payload),
		StartOffset: 1000,
		EndOffset:   1000 + int64(len(payload)),
	}

	if _, err := c.Write(chunk); err != nil {
		t.Fatalf("Write: %v", err)
	}

	matches, err := filepath.Glob(filepath.Join(cfg.LogChunksDir, "log.*.gz"))
	if err != nil || len(matches) == 0 {
		t.Fatalf("no chunk files found in %s", cfg.LogChunksDir)
	}

	f, _ := os.Open(matches[0])
	defer f.Close()
	gz, _ := gzip.NewReader(f)
	got, _ := io.ReadAll(gz)
	if string(got) != payload {
		t.Errorf("content mismatch: got %d bytes, want %d bytes", len(got), len(payload))
	}
}
