package binary_phasor

import (
	"encoding/binary"
	"errors"
	"math"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/metric"
	"github.com/influxdata/telegraf/testutil"
)

func generatePhasorBinary(iou byte, pf int, ts uint32) ([]byte, error) {
	var data []byte
	var chanNum byte
	switch iou {
	case 1:
		data = make([]byte, 0, 11306)
		chanNum = 14
	case 2:
		data = make([]byte, 0, 14106)
		chanNum = 15
	default:
		return nil, errors.New("not supported type")
	}

	data = binary.LittleEndian.AppendUint32(data, ts)
	data = append(data, iou)
	data = append(data, chanNum)

	for i := range 8 {
		for range pf {
			data = binary.LittleEndian.AppendUint64(data, math.Float64bits(float64(i)))
			data = binary.LittleEndian.AppendUint64(data, math.Float64bits(float64(i)))
			data = binary.LittleEndian.AppendUint64(data, math.Float64bits(float64(i)))
		}
	}

	for range pf {
		data = append(data, 0xAA) // 1010 1010
	}
	for range pf {
		data = append(data, 0x55) // 0101 0101
	}

	switch iou {
	case 1:
		for i := range 4 {
			for range pf {
				data = binary.LittleEndian.AppendUint64(data, math.Float64bits(float64(i+8)))
			}
		}
	case 2:
		for i := range 2 {
			for range pf {
				data = binary.LittleEndian.AppendUint64(data, math.Float64bits(float64(i+8)))
			}
		}
		for i := range 3 {
			for range pf {
				data = binary.LittleEndian.AppendUint64(data, math.Float64bits(float64(i+10)))
				data = binary.LittleEndian.AppendUint64(data, math.Float64bits(float64(i+10)))
				data = binary.LittleEndian.AppendUint64(data, math.Float64bits(float64(i+10)))
			}
		}
	default:
		return nil, errors.New("not supported type")
	}

	return data, nil
}

func generateExpectedMetrics(device string, iou byte, pf int, ts uint32) ([]telegraf.Metric, error) {
	metrics := make([]telegraf.Metric, pf)

	var fields map[string]interface{}
	measurement := ""
	switch iou {
	case 1:
		measurement = "current"
		fields = make(map[string]interface{}, 44)
	case 2:
		measurement = "voltage"
		fields = make(map[string]interface{}, 51)
	default:
		return nil, errors.New("not supported type")
	}

	for i := range metrics {
		m := metric.New(
			measurement,
			map[string]string{"device": device},
			fields,
			time.Unix(int64(ts), int64(i*1e9/pf)),
		)

		for cj := range 8 {
			chanNo := strconv.Itoa(cj + 1)
			m.AddField("c"+chanNo+"_amp", float64(cj))
			m.AddField("c"+chanNo+"_pa", float64(cj))
			m.AddField("c"+chanNo+"_rms", float64(cj))
		}

		for cj := range 8 {
			chanNo := strconv.Itoa(cj + 1)
			m.AddField("i"+chanNo, uint8((1+math.Pow(-1, float64(cj+1)))/2))
		}
		for cj := range 8 {
			chanNo := strconv.Itoa(cj + 1 + 8)
			m.AddField("i"+chanNo, uint8((1+math.Pow(-1, float64(cj)))/2))
		}

		switch iou {
		case 1:
			for cj, channel := range []string{"p", "q", "s", "pf"} {
				m.AddField(channel, float64(cj+8))
			}
		case 2:
			for cj, channel := range []string{"f", "df"} {
				m.AddField(channel, float64(cj+8))
			}
			for cj, channel := range []string{"uab", "ubc", "uca"} {
				m.AddField(channel+"_amp", float64(cj+10))
				m.AddField(channel+"_pa", float64(cj+10))
				m.AddField(channel+"_rms", float64(cj+10))
			}
		default:
			return nil, errors.New("not supported type")
		}

		metrics[i] = m
	}

	return metrics, nil
}

func TestParse(t *testing.T) {
	topic := "ssu001_Phasor"
	device, _ := strings.CutSuffix(topic, "_Phasor")

	for iou := range 2 {
		ts := uint32(time.Now().Unix())

		data, err := generatePhasorBinary(byte(iou+1), 50, ts)
		if err != nil {
			t.FailNow()
		}

		expected, err := generateExpectedMetrics(device, byte(iou+1), 50, ts)
		if err != nil {
			t.FailNow()
		}

		parser := new(Parser)
		parser.Init()

		actual, err := parser.Parse(data)
		if err != nil {
			t.FailNow()
		}

		testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime(), testutil.SortMetrics())
	}
}
