package worker

import (
	"bytes"
	"fmt"
	"os"
	"reflect"
	"slices"
	"testing"

	"gordenko.dev/dima/qb"
	"gordenko.dev/dima/qb/enc"
	"gordenko.dev/dima/qb/storage"
)

func TestWriteReadSnapshot(t *testing.T) {
	storage.DataPageSize = 32
	storage.DataPagePayloadSize = 20
	storage.IndexPageSize = 16

	buf := []byte{
		// values
		0x8f, // base value
		0x80, // delta 0
		0x01, // run (len = 3)
		0x81, // delta 1
		0x81, // literal (len = 2)
		// empty space
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00,
		// timestamps
		0x80,                   // h-byte (literal, len=1)
		0x94,                   // delta 20
		0x01,                   // h-byte (run, len=3)
		0xbc,                   // delta 60
		0x28, 0x80, 0x24, 0x6a, // since
		// footer
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
	}

	databuf := buf[:storage.DataPagePayloadSize]
	timestampsSize := 8
	valuesSize := 5

	metrics := map[uint32]*Metric{
		1: {
			metricType: qb.Cumulative,
			fracDigits: 3,
			lastPageNo: 1000,
			buffer:     buf,
			timestamps: enc.NewTimeDeltaCompressor(databuf, timestampsSize),
			values:     enc.NewValueDeltaCompressor(qb.Cumulative, 3, databuf, valuesSize),
			indexLevelTails: []storage.IndexLevelTail{
				{
					Buffer: []byte{
						0x01, 0x01, 0x01, 0x01,
						0x02, 0x02, 0x02, 0x02,
						0x00,
						// footer
						0x00, 0x00, 0x00, 0x00,
						0x00, 0x00, 0x00,
					},
					RecordsCount: 1,
				},
			},
		},
	}
	in := WriteSnapshotIn{
		SnapshotNumber:        1,
		WriteBufferSize:       8 * 1024 * 1024,
		Metrics:               metrics,
		FrozenIndexPagesCount: 7,
		IndexPageNumbers: []uint32{
			1, 2, 3, 4, 5,
		},
		FrozenDataPagesCount: 8,
		DataPageNumbers: []uint32{
			6, 7, 8,
		},
	}

	fileName := "test.snapshot"

	err := writeSnapshot(fileName, in)
	if err != nil {
		t.Fatalf("writeSnapshot: %s", err)
	}

	out, err := ReadSnapshot(fileName)
	if err != nil {
		t.Fatal(err)
	}

	if out.FrozenIndexPagesCount != in.FrozenIndexPagesCount {
		t.Fatalf("FrozenIndexPagesCount: got %d are not equal expected %d",
			out.FrozenIndexPagesCount, in.FrozenIndexPagesCount)
	}
	if out.FrozenDataPagesCount != in.FrozenDataPagesCount {
		t.Fatalf("FrozenDataPagesCount: got %d are not equal expected %d",
			out.FrozenDataPagesCount, in.FrozenDataPagesCount)
	}
	if !slices.Equal(out.IndexPageNumbers, in.IndexPageNumbers) {
		t.Fatalf("IndexPageNumbers: got %v are not equal expected %v",
			out.IndexPageNumbers, in.IndexPageNumbers)
	}
	if !slices.Equal(out.DataPageNumbers, in.DataPageNumbers) {
		t.Fatalf("DataPageNumbers: got %v are not equal expected %v",
			out.DataPageNumbers, in.DataPageNumbers)
	}
	for metricID, replay := range out.Metrics {
		origin, ok := metrics[metricID]
		if !ok {
			t.Fatalf("decoded metricID %d not found in metrics", metricID)
		}
		err = cmpMetric(replay, origin)
		if err != nil {
			t.Fatalf("metric %d: %s", metricID, err)
		}
	}
	os.Remove(fileName)
}

func cmpMetric(replay *storage.ReplayMetric, origin *Metric) error {
	if replay.MetricType != origin.metricType {
		return fmt.Errorf("metricType: got %d are not equal expected %d",
			replay.MetricType, origin.metricType)
	}
	if replay.FracDigits != origin.fracDigits {
		return fmt.Errorf("fracDigits: got %d are not equal expected %d",
			replay.FracDigits, origin.fracDigits)
	}
	if replay.LastPageNo != origin.lastPageNo {
		return fmt.Errorf("lastPageNo: got %d are not equal expected %d",
			replay.LastPageNo, origin.lastPageNo)
	}
	if !reflect.DeepEqual(replay.IndexLevelTails, origin.indexLevelTails) {
		return fmt.Errorf("indexLevelTails: got %#v are not equal expected %#v",
			replay.IndexLevelTails, origin.indexLevelTails)
	}
	if !bytes.Equal(replay.Buf, origin.buffer) {
		return fmt.Errorf("buffer: got % x are not equal expected % x",
			replay.Buf, origin.buffer)
	}
	if replay.TimestampsSize != origin.timestamps.Size() {
		return fmt.Errorf("timestampsSize: got %d are not equal expected %d",
			replay.TimestampsSize, origin.timestamps.Size())
	}
	if replay.ValuesSize != origin.values.Size() {
		return fmt.Errorf("valuesSize: got %d are not equal expected %d",
			replay.ValuesSize, origin.values.Size())
	}
	return nil
}
