package proto

import (
	"fmt"
	"time"

	"gordenko.dev/dima/diploma"
	"gordenko.dev/dima/diploma/bin"
	"gordenko.dev/dima/diploma/bufreader"
)

const (
	TypeDeleteMeasures         byte = 1
	TypeListCurrentValues      byte = 2
	TypeListInstantMeasures    byte = 3
	TypeListCumulativeMeasures byte = 33

	TypeListInstantPeriods    byte = 4
	TypeListCumulativePeriods byte = 44
	TypeGetMetric             byte = 5
	TypeAddMetric             byte = 6

	TypeListAllInstantMeasures    byte = 8
	TypeListAllCumulativeMeasures byte = 88
	TypeAppendMeasure             byte = 10
	TypeAppendMeasures            byte = 11
	TypeDeleteMetric              byte = 12

	RespPartOfValue byte = 255
	RespEndOfValue  byte = 254
	RespError       byte = 253
	RespSuccess     byte = 252
	RespValue       byte = 251

	ErrNoMetric          = 1
	ErrDuplicate         = 2
	ErrWrongMetricType   = 3
	ErrWrongFracDigits   = 4
	ErrExpiredMeasure    = 5
	ErrNonMonotonicValue = 6
	ErrEmptyMetricID     = 7
	ErrInvalidRange      = 8
	ErrUnexpected        = 9
)

func ErrorCodeToText(code uint16) string {
	switch code {
	case ErrNoMetric:
		return "NoMetric"
	case ErrDuplicate:
		return "Duplicate"
	case ErrWrongMetricType:
		return "WrongMetricType"
	case ErrWrongFracDigits:
		return "WrongFracDigits"
	case ErrExpiredMeasure:
		return "ExpiredMeasure"
	case ErrNonMonotonicValue:
		return "NonMonotonicValue"
	case ErrEmptyMetricID:
		return "EmptyMetricID"
	case ErrInvalidRange:
		return "InvalidRange"
	case ErrUnexpected:
		return "Unexpected"
	default:
		return ""
	}
}

// common

type Metric struct {
	MetricID   uint32
	MetricType diploma.MetricType
	FracDigits int
}

type AppendError struct {
	MetricID  uint32
	ErrorCode uint16
}

type TimeBound struct {
	Year  int
	Month time.Month
	Day   int
}

type CumulativeMeasure struct {
	Timestamp uint32
	Value     float64
	Total     float64
}

type CumulativePeriod struct {
	Period     uint32
	Start      uint32
	End        uint32
	StartValue float64
	EndValue   float64
}

type InstantMeasure struct {
	Timestamp uint32
	Value     float64
}

type InstantPeriod struct {
	Period uint32
	Start  uint32
	End    uint32
	Min    float64
	Max    float64
	Avg    float64
}

type CurrentValue struct {
	MetricID  uint32
	Timestamp uint32
	Value     float64
}

// API reqs

type GetMetricReq struct {
	MetricID uint32
}

func ReadGetMetricReq(r *bufreader.BufferedReader) (m GetMetricReq, err error) {
	m.MetricID, err = bin.ReadUint32(r)
	if err != nil {
		err = fmt.Errorf("read req: %s", err)
		return
	}
	return
}

type ListCurrentValuesReq struct {
	MetricIDs []uint32
}

func ReadListCurrentValuesReq(r *bufreader.BufferedReader) (m ListCurrentValuesReq, err error) {
	qty, err := bin.ReadUint16(r)
	if err != nil {
		err = fmt.Errorf("read req: %s", err)
		return
	}

	for i := range int(qty) {
		var metricID uint32
		metricID, err = bin.ReadUint32(r)
		if err != nil {
			err = fmt.Errorf("read metricID (#%d): %s", i, err)
			return
		}
		m.MetricIDs = append(m.MetricIDs, metricID)
	}
	return
}

type AddMetricReq struct {
	MetricID   uint32
	MetricType diploma.MetricType
	FracDigits int
}

func ReadAddMetricReq(r *bufreader.BufferedReader) (m AddMetricReq, err error) {
	arr, err := r.ReadN(6)
	if err != nil {
		err = fmt.Errorf("read req: %s", err)
		return
	}
	return UnpackAddMetricReq(arr), nil
}

func UnpackAddMetricReq(arr []byte) (m AddMetricReq) {
	m.MetricID = bin.GetUint32(arr)
	m.MetricType = diploma.MetricType(arr[4])
	m.FracDigits = int(arr[5])
	return
}

type DeleteMetricReq struct {
	MetricID uint32
}

func ReadDeleteMetricReq(r *bufreader.BufferedReader) (m DeleteMetricReq, err error) {
	m.MetricID, err = bin.ReadUint32(r)
	if err != nil {
		err = fmt.Errorf("read req: %s", err)
		return
	}
	return
}

type DeleteMeasuresReq struct {
	MetricID uint32
	Since    uint32 // timestamp (optional)
}

func ReadDeleteMeasuresReq(r *bufreader.BufferedReader) (m DeleteMeasuresReq, err error) {
	arr, err := r.ReadN(8)
	if err != nil {
		err = fmt.Errorf("read req: %s", err)
		return
	}
	return UnpackDeleteMeasuresReq(arr), nil
}

func UnpackDeleteMeasuresReq(arr []byte) (m DeleteMeasuresReq) {
	m.MetricID = bin.GetUint32(arr)
	m.Since = bin.GetUint32(arr[4:])
	return
}

type AppendMeasureReq struct {
	MetricID  uint32
	Timestamp uint32
	Value     float64
}

func ReadAppendMeasureReq(r *bufreader.BufferedReader) (m AppendMeasureReq, err error) {
	arr, err := r.ReadN(16)
	if err != nil {
		err = fmt.Errorf("read req: %s", err)
		return
	}
	return UnpackAppendMeasureReq(arr), nil
}

func UnpackAppendMeasureReq(arr []byte) (m AppendMeasureReq) {
	m.MetricID = bin.GetUint32(arr)
	m.Timestamp = bin.GetUint32(arr[4:])
	m.Value = bin.GetFloat64(arr[8:])
	return
}

type AppendMeasuresReq struct {
	MetricID uint32
	Measures []Measure
}

type Measure struct {
	Timestamp uint32
	Value     float64
}

func ReadAppendMeasuresReq(r *bufreader.BufferedReader) (m AppendMeasuresReq, err error) {
	prefix, err := bin.ReadN(r, 6) // metricID + measures qty
	if err != nil {
		err = fmt.Errorf("read prefix: %s", err)
		return
	}

	m.MetricID = bin.GetUint32(prefix[0:])
	qty := bin.GetUint16(prefix[4:])

	for i := range int(qty) {
		var measure Measure
		measure.Timestamp, err = bin.ReadUint32(r)
		if err != nil {
			err = fmt.Errorf("read timestamp (#%d): %s", i, err)
			return
		}
		measure.Value, err = bin.ReadFloat64(r)
		if err != nil {
			err = fmt.Errorf("read value (#%d): %s", i, err)
			return
		}
		m.Measures = append(m.Measures, measure)
	}
	return
}

type MetricMeasure struct {
	MetricID  uint32
	Timestamp uint32
	Value     float64
}

func ReadAppendMeasurePerMetricReq(r *bufreader.BufferedReader) (measures []MetricMeasure, err error) {
	qty, err := bin.ReadUint16(r)
	if err != nil {
		return
	}
	var tmp = make([]byte, 16)
	for range int(qty) {
		err = bin.ReadNInto(r, tmp)
		if err != nil {
			return
		}
		measures = append(measures, MetricMeasure{
			MetricID:  bin.GetUint32(tmp[0:]),
			Timestamp: bin.GetUint32(tmp[4:]),
			Value:     bin.GetFloat64(tmp[8:]),
		})
	}
	return
}

type ListAllInstantMetricMeasuresReq struct {
	MetricID uint32
}

func ReadListAllInstantMeasuresReq(r *bufreader.BufferedReader) (m ListAllInstantMetricMeasuresReq, err error) {
	m.MetricID, err = bin.ReadUint32(r)
	if err != nil {
		err = fmt.Errorf("read req: %s", err)
		return
	}
	return
}

type ListAllCumulativeMeasuresReq struct {
	MetricID uint32
}

func ReadListAllCumulativeMeasuresReq(r *bufreader.BufferedReader) (m ListAllCumulativeMeasuresReq, err error) {
	m.MetricID, err = bin.ReadUint32(r)
	if err != nil {
		err = fmt.Errorf("read req: %s", err)
		return
	}
	return
}

type ListInstantMeasuresReq struct {
	MetricID uint32
	Since    uint32
	Until    uint32
}

func ReadListInstantMeasuresReq(r *bufreader.BufferedReader) (m ListInstantMeasuresReq, err error) {
	arr, err := r.ReadN(12)
	if err != nil {
		err = fmt.Errorf("read req: %s", err)
		return
	}
	return UnpackListInstantMeasuresReq(arr), nil
}

func UnpackListInstantMeasuresReq(arr []byte) (m ListInstantMeasuresReq) {
	m.MetricID = bin.GetUint32(arr[0:])
	m.Since = bin.GetUint32(arr[4:])
	m.Until = bin.GetUint32(arr[8:])
	return
}

type ListCumulativeMeasuresReq struct {
	MetricID uint32
	Since    uint32
	Until    uint32
}

func ReadListCumulativeMeasuresReq(r *bufreader.BufferedReader) (m ListCumulativeMeasuresReq, err error) {
	arr, err := r.ReadN(12)
	if err != nil {
		err = fmt.Errorf("read req: %s", err)
		return
	}
	return UnpackListCumulativeMeasuresReq(arr), nil
}

func UnpackListCumulativeMeasuresReq(arr []byte) (m ListCumulativeMeasuresReq) {
	m.MetricID = bin.GetUint32(arr)
	m.Since = bin.GetUint32(arr[4:])
	m.Until = bin.GetUint32(arr[8:])
	return
}

type ListInstantPeriodsReq struct {
	MetricID       uint32
	Since          TimeBound
	Until          TimeBound
	GroupBy        diploma.GroupBy
	AggregateFuncs byte
	FirstHourOfDay int
}

func ReadListInstantPeriodsReq(r *bufreader.BufferedReader) (m ListInstantPeriodsReq, err error) {
	arr, err := r.ReadN(15)
	if err != nil {
		err = fmt.Errorf("read req: %s", err)
		return
	}
	return UnpackListInstantPeriodsReq(arr), nil
}

func UnpackListInstantPeriodsReq(arr []byte) (m ListInstantPeriodsReq) {
	m.MetricID = bin.GetUint32(arr)
	m.Since = TimeBound{
		Year:  int(bin.GetUint16(arr[4:])),
		Month: time.Month(arr[6]),
		Day:   int(arr[7]),
	}
	m.Until = TimeBound{
		Year:  int(bin.GetUint16(arr[8:])),
		Month: time.Month(arr[10]),
		Day:   int(arr[11]),
	}
	m.GroupBy = diploma.GroupBy(arr[12])
	m.AggregateFuncs = arr[13]
	m.FirstHourOfDay = int(arr[14])
	return
}

type ListCumulativePeriodsReq struct {
	MetricID       uint32
	Since          TimeBound
	Until          TimeBound
	GroupBy        diploma.GroupBy
	FirstHourOfDay int
}

func ReadListCumulativePeriodsReq(r *bufreader.BufferedReader) (m ListCumulativePeriodsReq, err error) {
	arr, err := r.ReadN(14)
	if err != nil {
		err = fmt.Errorf("read req: %s", err)
		return
	}
	return UnpackListCumulativePeriodsReq(arr), nil
}

func UnpackListCumulativePeriodsReq(arr []byte) (m ListCumulativePeriodsReq) {
	m.MetricID = bin.GetUint32(arr[0:])
	m.Since = TimeBound{
		Year:  int(bin.GetUint16(arr[4:])),
		Month: time.Month(arr[6]),
		Day:   int(arr[7]),
	}
	m.Until = TimeBound{
		Year:  int(bin.GetUint16(arr[8:])),
		Month: time.Month(arr[10]),
		Day:   int(arr[11]),
	}
	m.GroupBy = diploma.GroupBy(arr[12])
	m.FirstHourOfDay = int(arr[13])
	return
}
