package main

import (
	"flag"
	"sync"
	"time"

	zp "go.uber.org/zap"
	"go.uber.org/zap/zapcore"

	"a.yandex-team.ru/library/go/core/log"
	"a.yandex-team.ru/library/go/core/log/zap"
)

const (
	histShift    = 10
	histOldShift = 86410
	histLen      = 60
	histPeriod   = 5
)

var (
	config  = flag.String("config", "config.yaml", "yaml config with signals")
	verbose = flag.Bool("verbose", false, "verbosity")
)

var (
	Log = zap.Must(zp.Config{
		Level:            zp.NewAtomicLevelAt(zp.InfoLevel),
		Encoding:         "console",
		OutputPaths:      []string{"stdout"},
		ErrorOutputPaths: []string{"stderr"},
		EncoderConfig: zapcore.EncoderConfig{
			MessageKey:     "msg",
			LevelKey:       "level",
			TimeKey:        "ts",
			CallerKey:      "caller",
			EncodeLevel:    zapcore.CapitalColorLevelEncoder,
			EncodeTime:     zapcore.ISO8601TimeEncoder,
			EncodeDuration: zapcore.StringDurationEncoder,
			EncodeCaller:   zapcore.ShortCallerEncoder,
		},
	})
)

func dataScheduler(workers int, stopChan chan interface{}, idxChan chan int) {
	for {
		select {
		case <-stopChan:
			return
		default:
		}

		for i := 0; i < workers; i++ {
			if *verbose {
				Log.Info("Scheduling task", log.Any("index", i))
			}
			idxChan <- i
		}

		time.Sleep(1 * time.Second)
	}
}

func dataProcessor(config *Config, stopChan chan interface{}, idxChan chan int, dataChan chan PushData) {
	client, err := NewYasmHTTPApi()
	if err != nil {
		Log.Error("Error creating yasm client", log.Error(err))
		return
	}

	for {
		select {
		case <-stopChan:
			return
		default:
		}

		for idx := range idxChan {
			et := time.Now().Unix() - histShift
			st := et - histLen
			rNew, err := client.RequestHist(histPeriod, st, et, []string{config.Items[idx].SignalString})

			if err == nil {
				et = time.Now().Unix() - histOldShift
				st = et - histLen
				rOld, err := client.RequestHist(histPeriod, st, et, []string{config.Items[idx].SignalString})

				if err == nil {
					firstItem := 0
					if len(rNew) > 0 && len(rOld) > 0 {
						if rNew[firstItem].signal == rOld[firstItem].signal {
							newMedian := rNew[firstItem].values[len(rNew[firstItem].values)>>1]
							oldMedian := rOld[firstItem].values[len(rOld[firstItem].values)>>1]
							perc := newMedian / oldMedian * 100
							if *verbose {
								Log.Info("Got new data",
									log.Any("old", oldMedian), log.Any("new", newMedian),
									log.Any("perc", perc), log.Any("signal", config.Items[idx].SignalString))
							}

							dataChan <- PushData{idx, perc}
						} else {
							Log.Error("Could not match signals",
								log.Any("old", rOld[firstItem].signal),
								log.Any("new", rNew[firstItem].signal))
						}
					} else {
						Log.Error("Could not match length of data",
							log.Any("old", len(rOld)),
							log.Any("new", len(rNew)))
					}
				} else {
					Log.Error("Error requesting old data", log.Error(err))
				}
			} else {
				Log.Error("Error requesting new data", log.Error(err))
			}
		}
	}
}

type PushData struct {
	idx int
	val float64
}

func dataPusher(config *Config, stopChan chan interface{}, dataChan chan PushData) {
	client, err := NewYasmAgentHTTPApi()
	if err != nil {
		Log.Error("Error creating yasm client", log.Error(err))
		return
	}

	for {
		select {
		case <-stopChan:
			return
		default:
		}

		for st := range dataChan {
			data := YasmPushJSONRequest{config.Items[st.idx].OutputSignal,
				map[string]string{
					"ctype": config.Items[st.idx].DestTags.Ctype,
					"itype": config.Items[st.idx].DestTags.Itype,
					"prj":   config.Items[st.idx].DestTags.Prj},
				st.val,
			}

			resp, err := client.PushData(data)
			if err != nil {
				Log.Error("Could not push data",
					log.Any("signal", config.Items[st.idx].OutputSignal),
					log.Error(err))
				continue
			}

			if *verbose {
				Log.Info("Pushing data", log.Any("value", st.val), log.Any("signal", config.Items[st.idx].OutputSignal))
				Log.Info("Got response from yasm", log.Any("body", string(resp)))
			}
		}
	}
}

func main() {
	flag.Parse()

	config, err := NewConfig(*config)
	if err != nil {
		Log.Error("Error parsing config", log.Error(err))
		return
	}
	workers := len(config.Items)

	stopChan := make(chan interface{})
	idxChan := make(chan int, workers)
	dataChan := make(chan PushData, workers)

	wg := &sync.WaitGroup{}

	for i := 0; i < workers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			dataProcessor(config, stopChan, idxChan, dataChan)
		}()

		wg.Add(1)
		go func() {
			defer wg.Done()
			dataPusher(config, stopChan, dataChan)
		}()
	}

	dataScheduler(workers, stopChan, idxChan)

	close(stopChan)
	wg.Wait()
}
