package main

import (
	"database/sql"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
	"text/template"
	"time"

	"github.com/go-echarts/go-echarts/v2/charts"
	"github.com/go-echarts/go-echarts/v2/opts"
	_ "github.com/go-sql-driver/mysql"
	ini "gopkg.in/ini.v1"
)

var verb bool = false
var file string = ""
var cfg *ini.File

type DataSet struct {
	Alias      string
	Item       string
	Result     string
	UpdateTime string
}

func main() {
	flag.BoolVar(&verb, "v", false, "Show Verbose Debug Messages")
	flag.StringVar(&file, "f", "", "Config File Path")
	flag.Parse()

	log.SetOutput(os.Stdout)
	log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)

	if file == "" {
		file = strings.TrimSuffix(os.Args[0], ".exe") + ".ini"
	}

	var err error
	cfg, err = ini.Load(file)
	if err != nil {
		log.Printf("[ERR]  %v\r\n", err)
		os.Exit(1)
	}

	addr := cfg.Section("HTTP").Key("ListenIP").String()
	port := cfg.Section("HTTP").Key("ListenPort").String()
	http.HandleFunc("/", handleIndex)
	http.HandleFunc("/echart/", eChart)
	http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("./assets"))))
	http.ListenAndServe(addr+":"+port, nil)
}

func handleIndex(w http.ResponseWriter, r *http.Request) {
	if r.RequestURI == "/favicon.ico" {
		return
	}
	log.Printf("[MSG]  Received Request: %s, %s\r\n", r.RemoteAddr, r.RequestURI)
	defer r.Body.Close()

	type Data struct {
		Title string
		Host  string
	}
	data := Data{
		Title: cfg.Section("HTTP").Key("Title").String(),
		Host:  cfg.Section("HTTP").Key("Host").String(),
	}
	tpl, err := template.ParseFiles("root.html")
	if err != nil {
		log.Printf("[ERR]  %v\r\n", err)
		return
	}
	err = tpl.Execute(w, data)
	if err != nil {
		log.Printf("[ERR]  %v\r\n", err)
		return
	}
}

func eChart(w http.ResponseWriter, r *http.Request) {
	if r.RequestURI == "/favicon.ico" {
		return
	}
	fmt.Println()
	log.Printf("[MSG]  Received Request: %s, %s\r\n", r.RemoteAddr, r.RequestURI)
	defer r.Body.Close()

	if err := r.ParseForm(); err != nil {
		log.Printf("[ERR]  %v\r\n", err)
	}

	type Params struct {
		Range string ""
		Alias string ""
		Class string ""
		Scope string ""
	}
	var params Params
	params.Range = r.Form.Get("range")
	params.Scope = r.Form.Get("scope")
	params.Alias = r.Form.Get("alias")
	params.Class = r.Form.Get("class")
	log.Printf("[MSG]  Request Params: %#v\r\n", params)

	timeAxis := make([]string, 0)
	endTime := time.Now()
	beginTime := endTime.AddDate(0, 0, -30)
	bt := beginTime
	for i := 1; i <= 60*24*30; i++ {
		bt = bt.Add(time.Minute)
		timeAxis = append(timeAxis, bt.Format("01-02 15:04"))
	}

	// Parsing Config - Database
	cfg.Reload()
	dbHost := cfg.Section("Database").Key("Host").MustString("127.0.0.1")
	dbPort := cfg.Section("Database").Key("Port").MustString("3306")
	dbSchema := cfg.Section("Database").Key("Database").MustString("monitoring")
	dbTable := cfg.Section("Database").Key("Table").MustString("server")
	dbColumns := cfg.Section("Database").Key("Columns").MustString("alias,item,result,update_time")
	dbUsername := cfg.Section("Database").Key("Username").String()
	dbPassword := cfg.Section("Database").Key("Password").String()

	if dbUsername == "" || dbPassword == "" {
		log.Println("[ERR]  Invalid Database Configuration.")
		return
	}

	qry := fmt.Sprintf(
		"SELECT %s FROM %s WHERE alias='%s' AND item='%s' AND update_stmp>=%d ORDER BY seqid ASC;",
		dbColumns,
		dbTable,
		params.Alias,
		params.Class,
		beginTime.Unix(),
	)
	log.Printf("[MSG]  Query SQL: \r\n%s\r\n", qry)

	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbUsername, dbPassword, dbHost, dbPort, dbSchema)
	conn, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Printf("[ERR]  %v\r\n", err)
		return
	}
	defer conn.Close()

	if err := conn.Ping(); err != nil {
		log.Printf("[ERR]  %v\r\n", err)
		return
	}
	defer conn.Close()

	rows, err := conn.Query(qry)
	if err != nil {
		log.Printf("[ERR]  %v\r\n", err)
		return
	}
	defer rows.Close()
	log.Println("[MSG]  Query Successed.")

	baseData := make(map[string]string, 0)
	for rows.Next() {
		var data DataSet
		err := rows.Scan(&data.Alias, &data.Item, &data.Result, &data.UpdateTime)
		if err != nil {
			log.Printf("[ERR]  %v\r\n", err)
			return
		}
		baseData[data.UpdateTime] = data.Result
	}
	log.Printf("[MSG]  Total Scanned: %d\r\n", len(baseData))

	var dots []opts.LineData = make([]opts.LineData, 0)
	for _, t := range timeAxis {
		tmp, ok := baseData[t]
		if ok {
			dots = append(dots, opts.LineData{Value: tmp})
		} else {
			dots = append(dots, opts.LineData{Value: "null"})
		}
	}
	log.Println("[MSG]  Finished Sort Metadata.")

	// Parsing Config - Chart Titles
	chartTitle := cfg.Section("Chart").Key("Title").String()
	chartSubtitle := cfg.Section("Chart").Key("Subtitle").String()
	chartSeries := cfg.Section("Chart").Key("Series").String()
	assetsURL := cfg.Section("Chart").Key("AssetsURL").String()

	var xPercent float32 = 0
	if params.Range == "all" {
		xPercent = 0
	}
	if params.Range == "7days" {
		xPercent = 76.66
	}
	if params.Range == "3days" {
		xPercent = 90
	}
	if params.Range == "2days" {
		xPercent = 93.33
	}
	if params.Range == "1day" {
		xPercent = 96.66
	}
	if params.Range == "12hrs" {
		xPercent = 98.33
	}
	if params.Range == "6hrs" {
		xPercent = 99.16
	}
	if params.Alias != "" {
		chartSubtitle = params.Alias
	}
	if params.Class != "" {
		chartTitle = params.Class
	}

	chart := charts.NewLine()
	// lang := make([]string, 0)
	// lang = append(lang, "data view")
	// lang = append(lang, "turn off")
	// lang = append(lang, "refresh")

	chart.SetGlobalOptions(
		charts.WithTitleOpts(
			opts.Title{
				Left:  "center",
				Top:   "0",
				Title: chartTitle,
				TitleStyle: &opts.TextStyle{
					Color:      "#000000",
					FontSize:   18,
					FontFamily: "Calibri",
				},
				Subtitle: chartSubtitle,
				SubtitleStyle: &opts.TextStyle{
					Color:      "#222222",
					FontSize:   16,
					FontFamily: "Calibri",
				},
			},
		),
		charts.WithInitializationOpts(
			opts.Initialization{
				PageTitle:       "eChart",
				BackgroundColor: "#FFFFFF",
				Width:           "800px",
				Height:          "600px",
				AssetsHost:      assetsURL,
			},
		),
		charts.WithDataZoomOpts(
			opts.DataZoom{
				Type:  "inside",
				Start: xPercent,
				End:   100,
			},
		),
		charts.WithXAxisOpts(
			opts.XAxis{
				Min: "dataMin",
				Max: "dataMax",
				AxisLabel: &opts.AxisLabel{
					Show:         true,
					ShowMinLabel: true,
					ShowMaxLabel: true,
					Rotate:       45,
					FontSize:     "14",
					FontWeight:   "600",
					FontFamily:   "Calibri",
					LineHeight:   "250",
				},
			},
		),
		charts.WithTooltipOpts(
			opts.Tooltip{
				Show:    true,
				Trigger: "axis",
			},
		),
		charts.WithToolboxOpts(
			opts.Toolbox{
				Show: true,
				Feature: &opts.ToolBoxFeature{
					SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{
						Show: true,
						Type: "png",
					},
					DataView: &opts.ToolBoxFeatureDataView{
						Show: true,
					},
					Restore: &opts.ToolBoxFeatureRestore{
						Show: true,
					},
				},
			},
		),
		charts.WithLegendOpts(
			opts.Legend{
				Show: false,
			},
		),
	)

	chart.SetXAxis(timeAxis).AddSeries(chartSeries, dots).
		SetSeriesOptions(
			charts.WithLineChartOpts(
				opts.LineChart{
					ConnectNulls: true,
				},
			),
			charts.WithMarkPointNameTypeItemOpts(
				opts.MarkPointNameTypeItem{
					Name: "Max",
					Type: "max",
				},
				opts.MarkPointNameTypeItem{
					Name: "Min",
					Type: "min",
				},
			),
			charts.WithMarkPointStyleOpts(
				opts.MarkPointStyle{
					SymbolSize: 50,
					Label: &opts.Label{
						Show:     true,
						Position: "inside",
					},
				},
			),
			charts.WithMarkLineNameTypeItemOpts(
				opts.MarkLineNameTypeItem{
					Name: "Avrg",
					Type: "average",
				},
			),
			// charts.WithLabelOpts(
			// 	opts.Label{
			// 		Show:     true,
			// 		Position: "top",
			// 	},
			// ),
		)
	chart.Render(w)
	log.Println("[MSG]  Finished Render Web Page.")
}
