/*

 Copyright (c) [2023] [pcm]
 [pcm-coordinator] is licensed under Mulan PSL v2.
 You can use this software according to the terms and conditions of the Mulan PSL v2.
 You may obtain a copy of Mulan PSL v2 at:
          http://license.coscl.org.cn/MulanPSL2
 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 EITHER EXPaRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 See the Mulan PSL v2 for more details.

*/

package tracker

import (
	"errors"
	"fmt"
	"strconv"
	"time"

	jsoniter "github.com/json-iterator/go"
)

const (
	MetricTypeMatrix = "matrix"
	MetricTypeVector = "vector"
)

type Metadata struct {
	Metric string `json:"metric,omitempty" description:"metric name"`
	Type   string `json:"type,omitempty" description:"metric type"`
	Help   string `json:"help,omitempty" description:"metric description"`
}

type Metric struct {
	MetricName string `json:"metric_name,omitempty" description:"metric name, eg. scheduler_up_sum" csv:"metric_name"`
	MetricData `json:"data,omitempty" description:"actual metric result"`
	Error      string `json:"error,omitempty" csv:"-"`
}

type MetricValues []MetricValue

type MetricData struct {
	MetricType   string `json:"resultType,omitempty" description:"result type, one of matrix, vector" csv:"metric_type"`
	MetricValues `json:"result,omitempty" description:"metric data including labels, time series and values" csv:"metric_values"`
}

type DashboardEntity struct {
	GrafanaDashboardUrl     string `json:"grafanaDashboardUrl,omitempty"`
	GrafanaDashboardContent string `json:"grafanaDashboardContent,omitempty"`
	Description             string `json:"description,omitempty"`
	Namespace               string `json:"namespace,omitempty"`
}

// The first element is the timestamp, the second is the metric value.
// eg, [1585658599.195, 0.528]
type Point [2]float64

type MetricValue struct {
	Metadata map[string]string `json:"metric,omitempty" description:"time series labels"`
	// The type of Point is a float64 array with fixed length of 2.
	// So Point will always be initialized as [0, 0], rather than nil.
	// To allow empty Sample, we should declare Sample to type *Point
	Sample         *Point        `json:"value,omitempty" description:"time series, values of vector type"`
	Series         []Point       `json:"values,omitempty" description:"time series, values of matrix type"`
	ExportSample   *ExportPoint  `json:"exported_value,omitempty" description:"exported time series, values of vector type"`
	ExportedSeries []ExportPoint `json:"exported_values,omitempty" description:"exported time series, values of matrix type"`
}

func (mv *MetricValue) TransferToExportedMetricValue() {

	if mv.Sample != nil {
		sample := mv.Sample.transferToExported()
		mv.ExportSample = &sample
		mv.Sample = nil
	}

	for _, item := range mv.Series {
		mv.ExportedSeries = append(mv.ExportedSeries, item.transferToExported())
	}
	mv.Series = nil

}

func (p Point) Timestamp() float64 {
	return p[0]
}

func (p Point) Value() float64 {
	return p[1]
}

func (p Point) transferToExported() ExportPoint {
	return ExportPoint{p[0], p[1]}
}

func (p Point) Add(other Point) Point {
	return Point{p[0], p[1] + other[1]}
}

// MarshalJSON implements json.Marshaler. It will be called when writing JSON to HTTP response
// Inspired by prometheus/client_golang
func (p Point) MarshalJSON() ([]byte, error) {
	t, err := jsoniter.Marshal(p.Timestamp())
	if err != nil {
		return nil, err
	}
	v, err := jsoniter.Marshal(strconv.FormatFloat(p.Value(), 'f', -1, 64))
	if err != nil {
		return nil, err
	}
	return []byte(fmt.Sprintf("[%s,%s]", t, v)), nil
}

// UnmarshalJSON implements json.Unmarshaler. This is for unmarshaling test data.
func (p *Point) UnmarshalJSON(b []byte) error {
	var v []interface{}
	if err := jsoniter.Unmarshal(b, &v); err != nil {
		return err
	}

	if v == nil {
		return nil
	}

	if len(v) != 2 {
		return errors.New("unsupported array length")
	}

	ts, ok := v[0].(float64)
	if !ok {
		return errors.New("failed to unmarshal [timestamp]")
	}
	valstr, ok := v[1].(string)
	if !ok {
		return errors.New("failed to unmarshal [value]")
	}
	valf, err := strconv.ParseFloat(valstr, 64)
	if err != nil {
		return err
	}

	p[0] = ts
	p[1] = valf
	return nil
}

type CSVPoint struct {
	MetricName   string `csv:"metric_name"`
	Selector     string `csv:"selector"`
	Time         string `csv:"time"`
	Value        string `csv:"value"`
	ResourceUnit string `csv:"unit"`
}

type ExportPoint [2]float64

func (p ExportPoint) Timestamp() string {
	return time.Unix(int64(p[0]), 0).Format("2006-01-02 03:04:05 PM")
}

func (p ExportPoint) Value() float64 {
	return p[1]
}

func (p ExportPoint) Format() string {
	return p.Timestamp() + " " + strconv.FormatFloat(p.Value(), 'f', -1, 64)
}

func (p ExportPoint) TransformToCSVPoint(metricName string, selector string, resourceUnit string) CSVPoint {
	return CSVPoint{
		MetricName:   metricName,
		Selector:     selector,
		Time:         p.Timestamp(),
		Value:        strconv.FormatFloat(p.Value(), 'f', -1, 64),
		ResourceUnit: resourceUnit,
	}
}
