package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"cloud.google.com/go/bigquery"
	"google.golang.org/api/iterator"

	"strings"

	"flag"

	"path/filepath"

	"github.com/go-ini/ini"
	"github.com/influxdata/influxdb/client/v2"
)

const shortDate = "2006-01-02"

var (
	printDebugMsg = false
	printHelp     = false
)

type configdata struct {
	influxdbURL      string
	influxdbUser     string
	influxdbPass     string
	influxdbDatabase string
	projectID        string
	taskDescription  string
	bqSelectQuery    string
	dayToRunBq       string
	runDate          time.Time
	runDateUnix      int64
	taskMeasurement  string
	taskColumns      string
	taskValues       string
}

func main() {

	var iniFile, usageHint string
	var cd configdata
	_, prgName := filepath.Split(os.Args[0])
	usageHint = fmt.Sprintf("USAGE: %s --queryfile=ini_file_name [--debug] [--help]", prgName)

	paramQueryfile := flag.String("queryfile", "", "config file with query")
	paramHelp := flag.Bool("help", false, "print help")
	paramDebug := flag.Bool("debug", false, "print debug messages")
	flag.Parse()

	iniFile = *paramQueryfile
	printHelp = *paramHelp
	printDebugMsg = *paramDebug
	debugMsg(fmt.Sprintf("iniFile: %s", iniFile))
	debugMsg(fmt.Sprintf("printHelp: %v", printHelp))
	debugMsg(fmt.Sprintf("printDebugMsg: %v", printDebugMsg))

	if printHelp == true {
		log.Fatal(usageHint)
	}

	cd.runDate = time.Now().UTC()
	cd.runDateUnix = cd.runDate.Unix()
	cd.dayToRunBq = fmt.Sprintf("%d-%02d-%02d", cd.runDate.Year(), cd.runDate.Month(), cd.runDate.Day())
	fmt.Println(curTime(), "Aggregation for date:", cd.dayToRunBq)

	if _, err := os.Stat(iniFile); os.IsNotExist(err) {
		log.Fatal("ERROR: Cannot find ini file", iniFile, "message:", err)
	}

	cfg, err := ini.LoadSources(ini.LoadOptions{Loose: false}, iniFile)
	if err != nil {
		log.Fatal(curTime(), "ERROR: cannot read ini file:", iniFile, "message:", err)
	}

	cd.influxdbURL = cfg.Section("influxdb").Key("URL").String()
	checkValue("influxDB URL", cd.influxdbURL, true, printDebugMsg)

	cd.influxdbUser = cfg.Section("influxdb").Key("USER").String()
	checkValue("influxDB User", cd.influxdbUser, false, printDebugMsg)

	cd.influxdbPass = cfg.Section("influxdb").Key("PASSWORD").String()
	checkValue("influxDB PASSWORD", cd.influxdbPass, false, printDebugMsg)

	cd.influxdbDatabase = cfg.Section("influxdb").Key("DATABASE").String()
	checkValue("influxDB DATABASE", cd.influxdbDatabase, true, printDebugMsg)

	influxdb := influxDBClient(cd.influxdbURL, cd.influxdbUser, cd.influxdbPass)

	cd.projectID = "fitprediction"
	ctx := context.Background()
	bqDB, err := bigquery.NewClient(ctx, cd.projectID)
	if err != nil {
		log.Fatalln("Cannot create new BQ client: ", err)
	}

	sections := cfg.SectionStrings()
	for i := 0; i < len(sections); i++ {

		if sections[i] != "influxdb" && sections[i] != "DEFAULT" {
			checkValue("Section", sections[i], false, true)

			cd.taskDescription = cfg.Section(sections[i]).Key("TASK").String()
			checkValue("task Description", cd.taskDescription, false, true)

			cd.bqSelectQuery = cfg.Section(sections[i]).Key("QUERY").String()
			cd.bqSelectQuery = strings.Replace(cd.bqSelectQuery, "${YYYY-MM-DD}", cd.dayToRunBq, -1)
			checkValue("bigquery QUERY", cd.bqSelectQuery, false, false)

			cd.taskMeasurement = cfg.Section(sections[i]).Key("MEASUREMENT").String()
			checkValue("task MEASUREMENT", cd.taskMeasurement, true, printDebugMsg)

			cd.taskColumns = cfg.Section(sections[i]).Key("COLUMNS").String()
			checkValue("task COLUMNS", cd.taskColumns, true, printDebugMsg)

			cd.taskValues = cfg.Section(sections[i]).Key("VALUES").String()
			checkValue("task VALUES", cd.taskValues, true, printDebugMsg)

			bqQuery := bqDB.Query(cd.bqSelectQuery)

			bqQuery.QueryConfig.UseStandardSQL = true

			bqRows, err := bqQuery.Read(ctx)
			if err != nil {
				log.Fatalln("Cannot read data from BQ: ", err)
			}

			debugMsg("Starting inserts")
			rowCount := 0
			for {
				var values []bigquery.Value
				err := bqRows.Next(&values)
				if err == iterator.Done {
					break
				}
				if err != nil {
					log.Fatalln("Cannot read BQ data: ", err)
				}

				debugMsg(fmt.Sprint(values))
				insertGrafanaDashboards(influxdb, cd, values)
				rowCount++
			}
			fmt.Println(curTime(), "Rows processed:", rowCount)
		}
	}
	fmt.Println(curTime(), "DONE")
}

func insertGrafanaDashboards(c client.Client, cd configdata, data []bigquery.Value) {
	bp, err := client.NewBatchPoints(client.BatchPointsConfig{
		Database:  cd.influxdbDatabase,
		Precision: "s",
	})
	if err != nil {
		log.Fatalln(curTime(), "InfluxDB NewBatchPoints Error:", err)
	}

	tags := make(map[string]string)
	fields := make(map[string]interface{})

	columns := strings.Split(cd.taskColumns, ",")

	for i := range columns {
		col := columns[i]
		if strings.Contains(cd.taskValues, col) {
			// values
			debugMsg(fmt.Sprint(i, ":", col, ":", data[i], " - value"))
			fields[col] = fmt.Sprintf("%v", data[i])
		} else {
			//tags
			debugMsg(fmt.Sprint(i, ":", col, ":", data[i], " - tag"))
			tags[col] = fmt.Sprint(data[i])
		}

	}

	point, err := client.NewPoint(
		cd.taskMeasurement,
		tags,
		fields,
		time.Unix(cd.runDateUnix, 0),
	)
	if err != nil {
		log.Fatalln(curTime(), "NewPoint Error:", err)
	}

	bp.AddPoint(point)
	if err != nil {
		log.Fatal(curTime(), "addpoint error:", err)
	}

	//writing into influxdb
	err = c.Write(bp)
	if err != nil {
		log.Fatal(curTime(), "influx write error:", err)
	}

}

func curTime() string {
	return time.Now().UTC().Format(time.RFC3339) + ":"
}

func checkValue(name string, value string, required bool, printit bool) {
	if value == "" && required == true {
		log.Fatal("ERROR: variable ", name, " cannot be empty!")
	}
	if printit == true {
		fmt.Println(curTime(), name, ": ", value)
	}
}

func debugMsg(t string) {
	if printDebugMsg == true {
		fmt.Println(curTime(), t)
	}
}

func influxDBClient(influxuri string, influxuser string, influxpass string) client.Client {
	c, err := client.NewHTTPClient(client.HTTPConfig{
		Addr:     influxuri,
		Username: influxuser,
		Password: influxpass,
	})
	if err != nil {
		log.Fatalln(curTime(), "InfluxDB error:", err)
	}
	return c
}