package mqe

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

type periodProducer interface {
	Next() (time.Time, PeriodBound, PeriodBound, bool, error)
	Last() (PeriodBound, bool, error)
}

type PeriodBound struct {
	Time  int64
	Value float64
}

type PeriodProducer struct {
	metricID          int64
	parsePeriodLayout string
	location          *time.Location
	until             int64
	corrections       []_f64Correction
	rows              *sql.Rows
	tx                *sql.Tx
}

type PeriodProducerOptions struct {
	MetricID          int64
	ParsePeriodLayout string
	Location          *time.Location
	Until             int64
	Corrections       []_f64Correction
	Rows              *sql.Rows
	Tx                *sql.Tx
}

func NewPeriodProducer(opt PeriodProducerOptions) *PeriodProducer {
	if opt.Location == nil {
		panic("Location option is required")
	}

	if opt.MetricID == 0 {
		panic("MetricID option is required")
	}

	if opt.ParsePeriodLayout == "" {
		panic("ParsePeriodLayout option is required")
	}

	if opt.Until == 0 {
		panic("Until option is required")
	}

	if opt.Rows == nil {
		panic("Rows option is required")
	}

	if opt.Tx == nil {
		panic("Tx option is required")
	}

	s := new(PeriodProducer)
	s.metricID = opt.MetricID
	s.parsePeriodLayout = opt.ParsePeriodLayout
	s.location = opt.Location
	s.until = opt.Until
	s.corrections = opt.Corrections
	s.rows = opt.Rows
	s.tx = opt.Tx
	return s
}

func (s *PeriodProducer) Next() (period time.Time, first PeriodBound, last PeriodBound, found bool, err error) {
	var frameStr string

	hasNext := s.rows.Next()
	if hasNext {
		err = s.rows.Scan(&frameStr, &first.Time, &first.Value, &last.Time, &last.Value)
		if err != nil {
			return
		}

		first.Value = applyCorrectionsToMeasure(s.corrections, first.Time, first.Value)
		last.Value = applyCorrectionsToMeasure(s.corrections, last.Time, last.Value)

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

		found = true
	} else {
		err = s.rows.Err()
	}
	return
}

func (s *PeriodProducer) Last() (next PeriodBound, found bool, err error) {
	// Находим следующее показание метрики.
	err = s.tx.QueryRow(`
	SELECT tm, value, true
	FROM f64
	WHERE metricID=? AND tm > ?
	ORDER BY tm ASC
	LIMIT 1`,
		s.metricID, s.until).Scan(&next.Time, &next.Value, &found)

	if err != nil {
		if err != sql.ErrNoRows {
			return
		}
		// Нет записей
		err = nil
	}

	next.Value = applyCorrectionsToMeasure(s.corrections, next.Time, next.Value)
	return
}

// ДЛЯ ТЕСТИРОВАНИЯ

type stubPeriod struct {
	Period time.Time
	First  PeriodBound
	Last   PeriodBound
}

type StubPeriodProducerOptions struct {
	Last    *PeriodBound
	Periods []stubPeriod
}

func NewStubPeriodProducer(opt StubPeriodProducerOptions) *StubPeriodProducer {
	s := new(StubPeriodProducer)
	s.last = opt.Last
	s.periods = opt.Periods
	return s
}

type StubPeriodProducer struct {
	last    *PeriodBound // для отдельного запроса
	periods []stubPeriod
	idx     int
}

func (s *StubPeriodProducer) Next() (period time.Time, first PeriodBound, last PeriodBound, found bool, err error) {
	if s.idx < len(s.periods) {
		p := s.periods[s.idx]
		s.idx++
		period = p.Period
		first = p.First
		last = p.Last
		found = true
	}
	return
}

func (s *StubPeriodProducer) Last() (next PeriodBound, found bool, err error) {
	if s.last != nil {
		next = *s.last
		found = true
	}
	return
}
