package mqe

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

	"gordenko.dev/dima/pretty"
	"gordenko.dev/dima/qb/timeutil"
)

////////////////////////////

type MetricMeasure struct {
	Time  int64   `json:"t"`
	Value float64 `json:"v"`
}

type MetricError struct {
	Time int64 `json:"time"`
	Code int64 `json:"code"`
}

type ReadingsFilter struct {
	Since     string `json:"since"`
	Until     string `json:"until"`
	MetricIDs []int64
}

type MetricReadings struct {
	MetricID int64
	Measures []MetricMeasure
	Errors   []MetricError
}

// SelectReadingsInParallel - метод достает из БД данные всех метрик, которые связаны
// с объектом. Учитываются настройки объекта (LastDayOfMonth, FirstHourOfDay), а также
// фильтры выбранные пользователем на странице объекта (Since, Until, GroupBy).
// ВАЖНО!
// Запросы по каждой метрике отправляются в БД в отдельной горутине, что позволяет
// в разы ускорить получение данных по объекту.
func (s *MeasureQueryEngine) SelectReadingsInParallel(req ReadingsFilter) (result []MetricReadings, err error) {
	pretty.PPrintln("SelectReadingsInParallel", req)

	if len(req.MetricIDs) == 0 {
		return
	}

	if req.Since == "" {
		err = fix.Field(EmptyValue, "since")
		return
	}

	if req.Until == "" {
		err = fix.Field(EmptyValue, "until")
		return
	}

	// ВАЖНО!
	// Если в строке нет таймзоны - Parse функция возвращает время в UTC.
	// Поэтому нужно использовать ParseInLocation!

	since, err := time.ParseInLocation("2006-01-02", req.Since, s.location)
	if err != nil {
		err = fix.Field(WrongValue, "since")
		return
	}

	until, err := time.ParseInLocation("2006-01-02", req.Until, s.location)
	if err != nil {
		err = fix.Field(WrongValue, "until")
		return
	}

	// Корректируем, ВСЕГДА на последнюю секунду суток
	until = timeutil.LastSecondInPeriod(until, string(ByDay))

	fmt.Printf(`SelectReadingsInParallel: {
	Since: %s
	Until: %s
	MetricIDs: %v
}
`, since, until, req.MetricIDs)
	// FIX проверить как добавляются часы в DST часовых поясах
	// Для req.FirstHourOfDay=4 since должен быть 4:00:00, until должен быть
	// 3:59:59 следующего дня

	sinceUnixtime := since.Unix()
	untilUnixtime := until.Unix()

	if sinceUnixtime >= untilUnixtime {
		err = fix.Error(WrongDateRange)
		return
	}

	// Важно вирутальная метрика зависит от реальных, которые к объекту могут быть
	// привязаны или нет. Если нет - не возвращать.

	var (
		tasks []*MetricReadingsTask
		wg    = new(sync.WaitGroup)
	)

	for _, metricID := range req.MetricIDs {
		task := &MetricReadingsTask{
			In: MetricMeasuresFilter{
				MetricID: metricID,
				Since:    sinceUnixtime,
				Until:    untilUnixtime,
			},
			WaitGroup: wg,
		}

		wg.Add(1)
		s.metricReadingsCh <- task
		tasks = append(tasks, task)
	}

	wg.Wait()

	for _, task := range tasks {
		if task.Err != nil {
			err = fmt.Errorf("get data for the metric %d: %s", task.In.MetricID, task.Err)
			return
		}
		result = append(result, task.Result)
	}
	return
}

// ListInstantMeasures - cписок показаний для юнитов без накопительного итога
// (например, Давление)
func (s *MeasureQueryEngine) listMetricReadings(req MetricMeasuresFilter) (result MetricReadings, err error) {
	result.MetricID = req.MetricID
	// buf, _ := json.MarshalIndent(req, "", "    ")
	// fmt.Printf("listInstantMeasures: %s\n\n", buf)

	tx, err := s.db.Driver().Begin()
	if err != nil {
		return
	}
	defer tx.Rollback()

	rows, err := tx.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()

	for rows.Next() {
		var measure MetricMeasure
		err = rows.Scan(&measure.Time, &measure.Value)
		if err != nil {
			return
		}
		// fix - для cumulative метрик учет поправок
		result.Measures = append(result.Measures, measure)
	}

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

	//for _, m := range result {
	//	fmt.Printf("raw: %d\t%f\n", m.Time, m.Value)
	//}
	//fmt.Printf("listInstantMeasures (qty): %d\n", len(result))

	return
}
