package mqe

import (
	"database/sql"
	"fmt"
	"time"
)

type CurrentValue struct {
	MetricID uint32
	Time     int64   `json:"t"`
	Value    float64 `json:"v"`
}

func (s *MeasureQueryEngine) ListCurrentValues(metricIDs []uint32) (result []CurrentValue, err error) {
	driver := s.db.Driver()

	for _, metricID := range metricIDs {
		var rows *sql.Rows
		rows, err = driver.Query(`
		SELECT tm, value
		FROM f64
		WHERE metricID=?
		ORDER BY tm DESC
		LIMIT 1`,
			metricID)
		if err != nil {
			if err == sql.ErrNoRows {
				err = nil
			}
			return
		}

		for rows.Next() {
			var (
				tm    int64
				value float64
			)
			err = rows.Scan(&tm, &value)
			if err != nil {
				return
			}
			result = append(result, CurrentValue{
				MetricID: metricID,
				Time:     tm,
				Value:    value,
			})
		}

		if err = rows.Err(); err != nil {
			return
		}
		rows.Close()
	}
	return
}

// ListInstantMeasures - cписок показаний мгновенных метрик (Температура, Давление, Расход)
// за за интервал без группировки
func (s *MeasureQueryEngine) listInstantMeasures(req MetricMeasuresFilter) (_ []Measure, err error) {
	rows, err := s.db.Driver().Query(`
	SELECT tm, value
	FROM f64
	WHERE metricID=? AND tm BETWEEN ? AND ?
	ORDER BY tm ASC`,
		req.MetricID, req.Since, req.Until)
	if err != nil {
		if err == sql.ErrNoRows {
			err = nil
		}
		return
	}
	defer rows.Close()

	var result []Measure

	for rows.Next() {
		var (
			tm    int64
			value float64
		)
		err = rows.Scan(&tm, &value)
		if err != nil {
			return
		}
		result = append(result, Measure{
			Time: tm,
			Values: []float64{
				value,
			},
		})
	}

	if err = rows.Err(); err != nil {
		return
	}
	return result, nil
}

type AggregatedInstantMeasuresIn struct {
	MetricID       int64   `json:"metricID"`
	Since          int64   `json:"since"`
	Until          int64   `json:"until"`
	GroupBy        GroupBy `json:"groupBy"`
	Flags          byte    `json:"flags"`          // из настроек по умолчанию либо из запроса
	LastDayOfMonth int     `json:"lastDayOfMonth"` // например, конец месяца 25 число
	FirstHourOfDay int     `json:"firstHourOfDay"` // например день начинается в 6:00
}

// Заполняет поля Period и Values (от 1 до 3 значений) в структуре AggregatedMeasure
func (s *MeasureQueryEngine) listAggregatedInstantMeasures(req AggregatedInstantMeasuresIn) (_ []AggregatedMeasure, err error) {
	var (
		groupByFormat     string
		parsePeriodLayout string

		result []AggregatedMeasure
	)

	switch req.GroupBy {
	case ByHour:
		groupByFormat = "%Y%m%d%H"
		parsePeriodLayout = hourPeriodLayout

	case ByDay:
		groupByFormat = "%Y%m%d"
		parsePeriodLayout = dayPeriodLayout

	case ByMonth:
		groupByFormat = "%Y%m"
		parsePeriodLayout = monthPeriodLayout

	default:
		// Защита от дурака
		err = fmt.Errorf("unknown GroupBy: %s", req.GroupBy)
		return
	}

	valuesQty := 0
	values := ""

	if (req.Flags & AggregateMin) == AggregateMin {
		values += " MIN(value),"
		valuesQty++
	}

	if (req.Flags & AggregateMax) == AggregateMax {
		values += " MAX(value),"
		valuesQty++
	}

	if (req.Flags & AggregateAvg) == AggregateAvg {
		values += " AVG(value),"
		valuesQty++
	}

	dt, err := getPeriodDateTime(req.GroupBy, req.LastDayOfMonth, req.FirstHourOfDay)
	if err != nil {
		return
	}

	query := fmt.Sprintf(`
	SELECT%s DATE_FORMAT(%s, '%s') as periodStr
	FROM f64
	WHERE metricID=? AND tm BETWEEN ? AND ?
	GROUP BY periodStr
	ORDER BY periodStr ASC`,
		values, dt, groupByFormat)

	rows, err := s.db.Driver().Query(query, req.MetricID, req.Since, req.Until)
	if err != nil {
		if err == sql.ErrNoRows {
			err = nil
		}
		return
	}

	for rows.Next() {
		var (
			m = AggregatedMeasure{
				Values: make([]float64, valuesQty),
			}

			periodStr string
			period    time.Time

			valueIdx int
			dest     []interface{}
		)

		if (req.Flags & AggregateMin) == AggregateMin {
			dest = append(dest, &m.Values[valueIdx])
			valueIdx++
		}

		if (req.Flags & AggregateMax) == AggregateMax {
			dest = append(dest, &m.Values[valueIdx])
			valueIdx++
		}

		if (req.Flags & AggregateAvg) == AggregateAvg {
			dest = append(dest, &m.Values[valueIdx])
		}

		dest = append(dest, &periodStr)

		err = rows.Scan(dest...)
		if err != nil {
			return
		}

		period, err = time.ParseInLocation(parsePeriodLayout, periodStr, s.location)
		if err != nil {
			err = fmt.Errorf("time.ParseInLocation: %s; layout=%q; str=%q",
				err, parsePeriodLayout, periodStr)
			return
		}
		m.Period = period.Unix()

		result = append(result, m)
	}

	if err = rows.Err(); err != nil {
		return
	}
	return result, nil
}
