package main

import (
	"flag"
	"fmt"
	"log"
	"math/rand/v2"
	"os"
	"time"

	"gopkg.in/ini.v1"
	"gordenko.dev/dima/qb/client"
	"gordenko.dev/dima/qb/proto"
)

var (
	metricTypeToName = []string{
		"",
		"cumulative",
		"instant",
	}
)

func main() {
	var (
		iniFileName string
	)

	flag.Usage = func() {
		fmt.Fprint(flag.CommandLine.Output(), helpMessage)
		fmt.Fprint(flag.CommandLine.Output(), configExample)
		fmt.Fprintf(flag.CommandLine.Output(), mainUsage, os.Args[0])
		flag.PrintDefaults()
	}
	flag.StringVar(&iniFileName, "c", "requests.ini", "path to *.ini config file")
	flag.Parse()

	config, err := loadConfig(iniFileName)
	if err != nil {
		log.Fatalln(err)
	}

	conn, err := client.Connect(config.DatabaseAddr)
	if err != nil {
		log.Fatalf("client.Connect(%s): %s\n", config.DatabaseAddr, err)
	} else {
		fmt.Println("Connected to database")
	}

	sendRequests(conn)
}

// CONFIG FILE

const mainUsage = `Usage:
	%s -c path/to/config.ini

`

const helpMessage = `Gordenko project. Example requests. Version: 1.0
created by Dmytro Gordenko, 1.e4.kc6@gmail.com
`

const configExample = `
requests.ini example:

databaseAddr    = :12345

`

type Config struct {
	DatabaseAddr string
}

func (s Config) String() string {
	return fmt.Sprintf(`starting options:
databaseAddr = %s
`,
		s.DatabaseAddr)
}

func loadConfig(iniFileName string) (_ Config, err error) {
	file, err := ini.Load(iniFileName)
	if err != nil {
		return
	}

	conf := Config{}
	top := file.Section("")

	conf.DatabaseAddr = top.Key("databaseAddr").String()
	return conf, nil
}

func sendRequests(conn *client.Connection) {
	var (
		//instantMetricID uint32 = 10000
		cumulativeMetricID uint32 = 10001
		fracDigits         byte   = 2
		err                error
	)

	_ = cumulativeMetricID
	_ = fracDigits

	//conn.DeleteMetric(instantMetricID)
	//conn.DeleteMetric(cumulativeMetricID)

	// ADD INSTANT METRIC

	// err = conn.AddMetric(proto.AddMetricReq{
	// 	MetricID:   cumulativeMetricID,
	// 	MetricType: qb.Instant,
	// 	FracDigits: fracDigits,
	// })
	// if err != nil {
	// 	log.Fatalf("conn.AddMetric: %s\n", err)
	// } else {
	// 	fmt.Printf("\nInstant metric %d added\n", cumulativeMetricID)
	// }

	// GET INSTANT METRIC

	// iMetric, err := conn.GetMetric(cumulativeMetricID)
	// if err != nil {
	// 	log.Fatalf("conn.GetMetric: %s\n", err)
	// } else {
	// 	fmt.Printf(`
	// GetMetric:
	//   metricID: %d
	//   metricType: %s
	//   fracDigits: %d
	// `,
	// 		iMetric.MetricID, metricTypeToName[iMetric.MetricType], fracDigits)
	// }

	// APPEND MEASURES

	// instantMeasures := GenerateInstantMeasures(62, 220)

	// err = conn.AppendMeasures(proto.AppendMeasuresReq{
	// 	MetricID: instantMetricID,
	// 	Measures: instantMeasures,
	// })
	// if err != nil {
	// 	log.Fatalf("conn.AppendMeasures: %s\n", err)
	// } else {
	// 	fmt.Printf("\nAppended %d measures for the metric %d\n",
	// 		len(instantMeasures), instantMetricID)
	// }

	// 	// LIST INSTANT MEASURES

	// 	lastTimestamp := instantMeasures[len(instantMeasures)-1].Timestamp
	// 	until := time.Unix(int64(lastTimestamp), 0)
	// 	since := until.Add(-5 * time.Hour)

	// 	instantList, err := conn.ListInstantMeasures(proto.ListInstantMeasuresReq{
	// 		MetricID: instantMetricID,
	// 		Since:    uint32(since.Unix()),
	// 		Until:    uint32(until.Unix()),
	// 	})
	// 	if err != nil {
	// 		log.Fatalf("conn.ListInstantMeasures: %s\n", err)
	// 	} else {
	// 		fmt.Printf("\nListInstantMeasures %s - %s:\n",
	// 			formatTime(uint32(since.Unix())), formatTime(uint32(until.Unix())))
	// 		for _, item := range instantList {
	// 			fmt.Printf("  %s => %.2f\n", formatTime(item.Timestamp), item.Value)
	// 		}
	// 	}

	// 	// LIST ALL INSTANT MEASURES

	// 	instantList, err = conn.ListAllInstantMeasures(instantMetricID)
	// 	if err != nil {
	// 		log.Fatalf("conn.ListAllInstantMeasures: %s\n", err)
	// 	} else {
	// 		fmt.Printf("\nListAllInstantMeasures (last 15 items):\n")
	// 		for _, item := range instantList[:15] {
	// 			fmt.Printf("  %s => %.2f\n", formatTime(item.Timestamp), item.Value)
	// 		}
	// 	}

	// 	// LIST INSTANT PERIODS (group by hour)

	// 	until = time.Unix(int64(lastTimestamp+1), 0)
	// 	since = until.Add(-24 * time.Hour)

	// 	instantPeriods, err := conn.ListInstantPeriods(proto.ListInstantPeriodsReq{
	// 		MetricID: instantMetricID,
	// 		Since: proto.TimeBound{
	// 			Year:  since.Year(),
	// 			Month: since.Month(),
	// 			Day:   since.Day(),
	// 		},
	// 		Until: proto.TimeBound{
	// 			Year:  until.Year(),
	// 			Month: until.Month(),
	// 			Day:   until.Day(),
	// 		},
	// 		GroupBy:        qb.GroupByHour,
	// 		AggregateFuncs: qb.AggregateMin | qb.AggregateMax | qb.AggregateAvg,
	// 	})
	// 	if err != nil {
	// 		log.Fatalf("conn.ListInstantPeriods: %s\n", err)
	// 	} else {
	// 		fmt.Printf("\nListInstantPeriods (1 day, group by hour):\n")
	// 		for _, item := range instantPeriods {
	// 			fmt.Printf("  %s => min %.2f, max %.2f, avg %.2f\n", formatHourPeriod(item.Period), item.Min, item.Max, item.Avg)
	// 		}
	// 	}

	// 	// LIST INSTANT PERIODS (group by day)

	// 	until = time.Unix(int64(lastTimestamp+1), 0)
	// 	since = until.AddDate(0, 0, -7)

	// 	instantPeriods, err = conn.ListInstantPeriods(proto.ListInstantPeriodsReq{
	// 		MetricID: instantMetricID,
	// 		Since: proto.TimeBound{
	// 			Year:  since.Year(),
	// 			Month: since.Month(),
	// 			Day:   since.Day(),
	// 		},
	// 		Until: proto.TimeBound{
	// 			Year:  until.Year(),
	// 			Month: until.Month(),
	// 			Day:   until.Day(),
	// 		},
	// 		GroupBy:        qb.GroupByDay,
	// 		AggregateFuncs: qb.AggregateMin | qb.AggregateMax | qb.AggregateAvg,
	// 	})
	// 	if err != nil {
	// 		log.Fatalf("conn.ListInstantPeriods: %s\n", err)
	// 	} else {
	// 		fmt.Printf("\nListInstantPeriods (7 days, group by day):\n")
	// 		for _, item := range instantPeriods {
	// 			fmt.Printf("  %s => min %.2f, max %.2f, avg %.2f\n", formatDayPeriod(item.Period), item.Min, item.Max, item.Avg)
	// 		}
	// 	}

	// 	// LIST INSTANT PERIODS (group by month)

	// 	until = time.Unix(int64(lastTimestamp+1), 0)
	// 	since = until.AddDate(0, 0, -62)

	// 	instantPeriods, err = conn.ListInstantPeriods(proto.ListInstantPeriodsReq{
	// 		MetricID: instantMetricID,
	// 		Since: proto.TimeBound{
	// 			Year:  since.Year(),
	// 			Month: since.Month(),
	// 			Day:   since.Day(),
	// 		},
	// 		Until: proto.TimeBound{
	// 			Year:  until.Year(),
	// 			Month: until.Month(),
	// 			Day:   until.Day(),
	// 		},
	// 		GroupBy:        qb.GroupByMonth,
	// 		AggregateFuncs: qb.AggregateMin | qb.AggregateMax | qb.AggregateAvg,
	// 	})
	// 	if err != nil {
	// 		log.Fatalf("conn.ListInstantPeriods: %s\n", err)
	// 	} else {
	// 		fmt.Printf("\nListInstantPeriods (62 days, group by month):\n")
	// 		for _, item := range instantPeriods {
	// 			fmt.Printf("  %s => min %.2f, max %.2f, avg %.2f\n", formatMonthPeriod(item.Period), item.Min, item.Max, item.Avg)
	// 		}
	// 	}

	// 	// DELETE INSTANT METRIC MEASURES

	// 	err = conn.DeleteMeasures(proto.DeleteMeasuresReq{
	// 		MetricID: instantMetricID,
	// 	})
	// 	if err != nil {
	// 		log.Fatalf("conn.DeleteMeasures: %s\n", err)
	// 	} else {
	// 		fmt.Printf("\nInstant metric %d measures deleted\n", instantMetricID)
	// 	}

	// 	// DELETE INSTANT METRIC

	// 	err = conn.DeleteMetric(instantMetricID)
	// 	if err != nil {
	// 		log.Fatalf("conn.DeleteMetric: %s\n", err)
	// 	} else {
	// 		fmt.Printf("\nInstant metric %d deleted\n", instantMetricID)
	// 	}

	// ADD CUMULATIVE METRIC

	// err = conn.AddMetric(proto.AddMetricReq{
	// 	MetricID:   cumulativeMetricID,
	// 	MetricType: qb.Cumulative,
	// 	FracDigits: fracDigits,
	// })
	// if err != nil {
	// 	log.Fatalf("conn.AddMetric: %s\n", err)
	// } else {
	// 	fmt.Printf("\nCumulative metric %d added\n", cumulativeMetricID)
	// }

	// GET CUMULATIVE METRIC

	cMetric, err := conn.GetMetric(cumulativeMetricID)
	if err != nil {
		log.Fatalf("conn.GetMetric: %s\n", err)
	} else {
		fmt.Printf(`
	GetMetric:
	  metricID: %d
	  metricType: %s
	  fracDigits: %d
	`,
			cMetric.MetricID, metricTypeToName[cMetric.MetricType], cMetric.FracDigits)
	}

	// APPEND MEASURES

	// cumulativeMeasures := GenerateCumulativeMeasures(200)
	// if len(cumulativeMeasures) > 65535 {
	// 	cumulativeMeasures = cumulativeMeasures[:65535]
	// }

	// result, err := conn.AppendMeasures(proto.AppendMeasuresReq{
	// 	MetricID: cumulativeMetricID,
	// 	Measures: cumulativeMeasures,
	// })
	// if err != nil {
	// 	log.Fatalf("conn.AppendMeasures: %s\n", err)
	// } else {
	// 	fmt.Printf("\nAppended %d measures for the metric %d: count=%d, errorCode=%d\n",
	// 		len(cumulativeMeasures), cumulativeMetricID, result.WrittenCount, result.ErrorCode)
	// }

	// currentValues, err := conn.ListCurrentValues([]uint32{
	// 	cumulativeMetricID,
	// })
	// if err != nil {
	// 	log.Fatalf("conn.ListCurrentValues: %s\n", err)
	// } else {
	// 	for _, v := range currentValues {
	// 		fmt.Println(v.MetricID, v.Timestamp, v.Value)
	// 	}
	// }

	// LIST CUMULATIVE MEASURES

	// 	lastTimestamp = cumulativeMeasures[len(cumulativeMeasures)-1].Timestamp
	// 	until = time.Unix(int64(lastTimestamp), 0)
	// 	since = until.Add(-5 * time.Hour)

	// 	cumulativeList, err := conn.ListCumulativeMeasures(proto.ListCumulativeMeasuresReq{
	// 		MetricID: cumulativeMetricID,
	// 		Since:    uint32(since.Unix()),
	// 		Until:    uint32(until.Unix()),
	// 	})
	// 	if err != nil {
	// 		log.Fatalf("conn.ListCumulativeMeasures: %s\n", err)
	// 	} else {
	// 		fmt.Printf("\nListCumulativeMeasures %s - %s:\n",
	// 			formatTime(uint32(since.Unix())), formatTime(uint32(until.Unix())))

	// 		for _, item := range cumulativeList {
	// 			fmt.Printf("  %s => %.2f\n", formatTime(item.Timestamp), item.Value)
	// 		}
	// 	}

	// LIST ALL CUMULATIVE MEASURES

	var cumulativeList []proto.CumulativeMeasure
	cumulativeList, err = conn.ListAllCumulativeMeasures(cumulativeMetricID)
	if err != nil {
		log.Fatalf("conn.ListAllCumulativeMeasures: %s\n", err)
	} else {
		fmt.Printf("\nListAllCumulativeMeasures (last 15 items):\n")
		//fmt.Printf("%#v\n", cumulativeList)
		for idx, item := range cumulativeList {
			fmt.Printf("  %d: %s => %.2f\n", idx, formatTime(item.Timestamp), item.Value)
		}
	}

	// 	// LIST CUMULATIVE PERIODS (group by hour)

	// 	until = time.Unix(int64(lastTimestamp+1), 0)
	// 	since = until.Add(-24 * time.Hour)

	// 	cumulativePeriods, err := conn.ListCumulativePeriods(proto.ListCumulativePeriodsReq{
	// 		MetricID: cumulativeMetricID,
	// 		Since: proto.TimeBound{
	// 			Year:  since.Year(),
	// 			Month: since.Month(),
	// 			Day:   since.Day(),
	// 		},
	// 		Until: proto.TimeBound{
	// 			Year:  until.Year(),
	// 			Month: until.Month(),
	// 			Day:   until.Day(),
	// 		},
	// 		GroupBy: qb.GroupByHour,
	// 	})
	// 	if err != nil {
	// 		log.Fatalf("conn.ListCumulativePeriods: %s\n", err)
	// 	} else {
	// 		fmt.Printf("\nListCumulativePeriods (1 day, group by hour):\n")
	// 		for _, item := range cumulativePeriods {
	// 			fmt.Printf("  %s => end value %.2f, total %.2f\n", formatHourPeriod(item.Period), item.EndValue, item.Total)
	// 		}
	// 	}

	// 	// LIST CUMULATIVE PERIODS (group by day)

	// 	until = time.Unix(int64(lastTimestamp+1), 0)
	// 	since = until.AddDate(0, 0, -7)

	// 	cumulativePeriods, err = conn.ListCumulativePeriods(proto.ListCumulativePeriodsReq{
	// 		MetricID: cumulativeMetricID,
	// 		Since: proto.TimeBound{
	// 			Year:  since.Year(),
	// 			Month: since.Month(),
	// 			Day:   since.Day(),
	// 		},
	// 		Until: proto.TimeBound{
	// 			Year:  until.Year(),
	// 			Month: until.Month(),
	// 			Day:   until.Day(),
	// 		},
	// 		GroupBy: qb.GroupByDay,
	// 	})
	// 	if err != nil {
	// 		log.Fatalf("conn.ListCumulativePeriods: %s\n", err)
	// 	} else {
	// 		fmt.Printf("\nListCumulativePeriods (7 days, group by day):\n")
	// 		for _, item := range cumulativePeriods {
	// 			fmt.Printf("  %s => end value %.2f, total %.2f\n", formatDayPeriod(item.Period), item.EndValue, item.Total)
	// 		}
	// 	}

	// 	// LIST CUMULATIVE PERIODS (group by day)

	// 	until = time.Unix(int64(lastTimestamp+1), 0)
	// 	since = until.AddDate(0, 0, -62)

	// 	cumulativePeriods, err = conn.ListCumulativePeriods(proto.ListCumulativePeriodsReq{
	// 		MetricID: cumulativeMetricID,
	// 		Since: proto.TimeBound{
	// 			Year:  since.Year(),
	// 			Month: since.Month(),
	// 			Day:   since.Day(),
	// 		},
	// 		Until: proto.TimeBound{
	// 			Year:  until.Year(),
	// 			Month: until.Month(),
	// 			Day:   until.Day(),
	// 		},
	// 		GroupBy: qb.GroupByMonth,
	// 	})
	// 	if err != nil {
	// 		log.Fatalf("conn.ListCumulativePeriods: %s\n", err)
	// 	} else {
	// 		fmt.Printf("\nListCumulativePeriods (62 days, group by month):\n")
	// 		for _, item := range cumulativePeriods {
	// 			fmt.Printf("  %s => end value %.2f, total %.2f\n", formatMonthPeriod(item.Period), item.EndValue, item.Total)
	// 		}
	// 	}

	// 	// DELETE CUMULATIVE METRIC MEASURES

	// 	err = conn.DeleteMeasures(proto.DeleteMeasuresReq{
	// 		MetricID: cumulativeMetricID,
	// 	})
	// 	if err != nil {
	// 		log.Fatalf("conn.DeleteMeasures: %s\n", err)
	// 	} else {
	// 		fmt.Printf("\nCumulative metric %d measures deleted\n", cumulativeMetricID)
	// 	}

	// 	// DELETE CUMULATIVE METRIC

	// err = conn.DeleteMetric(cumulativeMetricID)
	//
	//	if err != nil {
	//		log.Fatalf("conn.DeleteMetric: %s\n", err)
	//	} else {
	//
	//		fmt.Printf("\nCumulative metric %d deleted\n", cumulativeMetricID)
	//	}
}

const datetimeLayout = "2006-01-02 15:04:05"

func formatTime(timestamp uint32) string {
	tm := time.Unix(int64(timestamp), 0)
	return tm.Format(datetimeLayout)
}

func formatHourPeriod(period uint32) string {
	tm := time.Unix(int64(period), 0)
	return tm.Format("2006-01-02 15:00 - 15") + ":59"
}

func formatDayPeriod(period uint32) string {
	tm := time.Unix(int64(period), 0)
	return tm.Format("2006-01-02")
}

func formatMonthPeriod(period uint32) string {
	tm := time.Unix(int64(period), 0)
	return tm.Format("2006-01")
}

func GenerateCumulativeMeasures(days int) []proto.Measure {
	var (
		measures    []proto.Measure
		minutes     = []int{14, 29, 44, 59}
		hoursPerDay = 24
		totalHours  = days * hoursPerDay
		since       = time.Now().AddDate(0, 0, -days)
		totalValue  float64
	)

	idx := 0
	for i := range totalHours {
		hourTime := since.Add(time.Duration(i) * time.Hour)
		for _, m := range minutes {
			measureTime := time.Date(
				hourTime.Year(),
				hourTime.Month(),
				hourTime.Day(),
				hourTime.Hour(),
				m, // minutes
				0, // seconds
				0, // nanoseconds
				time.Local,
			)

			measure := proto.Measure{
				Timestamp: uint32(measureTime.Unix()),
				Value:     totalValue,
			}
			measures = append(measures, measure)

			totalValue += rand.Float64()

			if idx < 1500 {
				fmt.Println(idx, measure.Timestamp)
			}
			idx++
		}
	}
	for i := 1; i < len(measures); i++ {
		if measures[i].Timestamp <= measures[i-1].Timestamp {
			fmt.Println("FUCK", i)
		}
	}
	return measures
}

func GenerateInstantMeasures(days int, baseValue float64) []proto.Measure {
	var (
		measures    []proto.Measure
		minutes     = []int{14, 29, 44, 59}
		hoursPerDay = 24
		totalHours  = days * hoursPerDay
		since       = time.Now().AddDate(0, 0, -days)
	)

	for i := range totalHours {
		hourTime := since.Add(time.Duration(i) * time.Hour)
		for _, m := range minutes {
			measureTime := time.Date(
				hourTime.Year(),
				hourTime.Month(),
				hourTime.Day(),
				hourTime.Hour(),
				m, // minutes
				0, // seconds
				0, // nanoseconds
				time.Local,
			)

			// value = +-10% from base value
			fluctuation := baseValue * 0.1
			value := baseValue + (rand.Float64()*2-1)*fluctuation

			measure := proto.Measure{
				Timestamp: uint32(measureTime.Unix()),
				Value:     value,
			}
			measures = append(measures, measure)
		}
	}
	return measures
}

//02 88 30 3e 88 31 1d 88 31 3e a3 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 28 68 05 6a
//02 88 30 3e 88 31 1d 88 31 3e a3 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 28 68 05 6a

//02 88 4b 4c 88 4c 06 88 4c 39 ae 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 30 1b 2c 6a
//02 88 4b 4c 88 4c 06 88 4c 39 ae 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 7f 04 87 30 1b 2c 6a
