package storage

import (
	"errors"
	"fmt"

	bin "gordenko.dev/dima/bin/little"
	"gordenko.dev/dima/qb"
	"gordenko.dev/dima/qb/enc"
	"gordenko.dev/dima/qb/util"
)

type BackwardCursor struct {
	metricType            qb.MetricType
	fracDigits            byte
	fetchDataPage         func(uint32) ([]byte, error)
	releasePage           func(uint32)
	pageNo                uint32
	pageData              []byte
	timestampDecompressor qb.TimestampDecompressor
	valueDecompressor     qb.ValueDecompressor
}

type BackwardCursorOptions struct {
	MetricType    qb.MetricType
	FracDigits    byte
	PageNo        uint32
	PageData      []byte
	FetchDataPage func(uint32) ([]byte, error)
	ReleasePage   func(uint32)
}

func NewBackwardCursor(opt BackwardCursorOptions) (*BackwardCursor, error) {
	fmt.Printf("footer: % x\n", opt.PageData[DataPageSize-DataPageFooterSize:])
	switch opt.MetricType {
	case qb.Instant, qb.Cumulative:
		// ok
	default:
		return nil, fmt.Errorf("MetricType option has wrong value: %d", opt.MetricType)
	}
	if opt.FracDigits > qb.MaxFracDigits {
		return nil, errors.New("FracDigits option is required")
	}
	if opt.FetchDataPage == nil {
		return nil, errors.New("FetchDataPage option is required")
	}
	if opt.ReleasePage == nil {
		return nil, errors.New("ReleasePage option is required")
	}
	if opt.PageNo == 0 {
		return nil, errors.New("PageNo option is required")
	}
	if len(opt.PageData) == 0 {
		return nil, errors.New("PageData option is required")
	}
	s := &BackwardCursor{
		metricType:    opt.MetricType,
		fracDigits:    opt.FracDigits,
		fetchDataPage: opt.FetchDataPage,
		releasePage:   opt.ReleasePage,
		pageNo:        opt.PageNo,
		pageData:      opt.PageData,
	}
	var err error
	s.timestampDecompressor, err = CreateTimestampDecompressor(s.pageData)
	if err != nil {
		return nil, fmt.Errorf("CreateTimestampDecompressor: %s", err)
	}
	s.valueDecompressor, err = CreateValueDecompressor(s.pageData, s.metricType, s.fracDigits)
	if err != nil {
		return nil, fmt.Errorf("CreateValueDecompressor: %s", err)
	}
	return s, nil
}

// timestamp, value, done, error
func (s *BackwardCursor) Prev() (uint32, float64, bool, error) {
	var (
		timestamp uint32
		value     float64
		done      bool
		err       error
	)
	timestamp, done = s.timestampDecompressor.NextValue()
	if !done {
		value, done = s.valueDecompressor.NextValue()
		if done {
			return 0, 0, false,
				fmt.Errorf("corrupted data page %d: has timestamp, no value",
					s.pageNo)
		}
		return timestamp, value, false, nil
	}

	prevPageNo, _ := bin.GetUint32(s.pageData[prevPageIdx:])
	fmt.Printf("CURSOR: prev pageNo %d\n", prevPageNo)
	if prevPageNo == 0 {
		return 0, 0, true, nil
	}
	s.releasePage(s.pageNo)

	s.pageNo = prevPageNo
	s.pageData, err = s.fetchDataPage(s.pageNo)
	if err != nil {
		return 0, 0, false,
			fmt.Errorf("fetchDataPage(%d): %s", s.pageNo, err)
	}
	s.timestampDecompressor, err = CreateTimestampDecompressor(s.pageData)
	if err != nil {
		return 0, 0, false,
			fmt.Errorf("CreateTimestampDecompressor: %s", err)
	}
	s.valueDecompressor, err = CreateValueDecompressor(s.pageData, s.metricType, s.fracDigits)
	if err != nil {
		return 0, 0, false,
			fmt.Errorf("CreateValueDecompressor: %s", err)
	}
	//
	timestamp, done = s.timestampDecompressor.NextValue()
	if done {
		return 0, 0, false,
			fmt.Errorf("corrupted data page %d: no timestamps", s.pageNo)
	}
	value, done = s.valueDecompressor.NextValue()
	if done {
		return 0, 0, false,
			fmt.Errorf("corrupted data page %d: no values", s.pageNo)
	}
	return timestamp, value, false, nil
}

func (s *BackwardCursor) Close() {
	s.releasePage(s.pageNo)
}

func CreateTimestampDecompressor(page []byte) (qb.TimestampDecompressor, error) {
	size, _ := bin.GetUint16(page[timestampsSizeIdx:])
	if int(size) > DataPagePayloadSize {
		return nil, fmt.Errorf("bug: invalid timestamps size %d", size)
	}
	pos := DataPagePayloadSize - int(size)
	d := enc.NewTimeDeltaDecompressor()
	d.RestoreFromEnd(page[pos:DataPagePayloadSize])

	payload := page[pos:DataPagePayloadSize]

	fmt.Printf("PAGE timestamps (CURSOR) %d:\n% x\n", len(payload), payload)
	return d, nil
}

func CreateValueDecompressor(page []byte, metricType qb.MetricType, fracDigits byte) (qb.ValueDecompressor, error) {
	size, _ := bin.GetUint16(page[valuesSizeIdx:])
	if int(size) > DataPagePayloadSize {
		return nil, fmt.Errorf("bug: invalid values size %d", size)
	}
	d := enc.NewValueDeltaDecompressor(metricType, fracDigits)
	d.RestoreFromEnd(page[:size])
	return d, nil
}

func VerifyDataPageCRC32(data []byte) error {
	var (
		calculatedCRC = util.CalculateCRC32(data[:dataCRC32Idx])
		writtenCRC, _ = bin.GetUint32(data[dataCRC32Idx:])
	)
	if calculatedCRC != writtenCRC {
		return fmt.Errorf("calculated CRC32 %d are not equal written CRC32 %d",
			calculatedCRC, writtenCRC)
	}
	return nil
}

func VerifyIndexPageCRC32(data []byte) error {
	var (
		calculatedCRC = util.CalculateCRC32(data[:indexCRC32Idx])
		writtenCRC, _ = bin.GetUint32(data[indexCRC32Idx:])
	)
	if calculatedCRC != writtenCRC {
		return fmt.Errorf("calculated CRC32 %d are not equal written CRC32 %d",
			calculatedCRC, writtenCRC)
	}
	return nil
}
