package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"text/template"

	grafana "code.justin.tv/live/autohost-grafana"
)

func main() {
	args := os.Args
	if len(args) != 2 {
		log.Fatalf(`Usage:
    ./generate-autohost-worker-dashboard OUT_FILE_PATH
`)
	}
	outputFilePath := args[1]

	err := run(outputFilePath)
	if err != nil {
		log.Fatalln(err)
	}
}

type Params struct {
	DashboardID        int
	DashboardTitle     string
	DashboardUID       string
	DataSourceVariable string
	RegionVariable     string
	Service            string
	StageVariable      string
	SubstageVariable   string
	Namespace          string
	DataSourceRegex    string

	MainLoopOps          []Operation
	UpdateLiveChannelsOp Operation
}

type Operation struct {
	Name         string
	Dependencies []string
}

type Dashboard struct {
	ID              int
	Title           string
	UID             string
	Panels          string
	AwsDatasources  []string
	Region          string
	Stages          []string
	DefaultStage    string
	Service         string
	DataSourceRegex string
}

func run(outputFilePath string) error {
	dashboardJson, err := createDashboardJson()
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(outputFilePath, []byte(dashboardJson), 0644)
	if err != nil {
		return err
	}

	return nil
}

func createDashboardJson() (string, error) {
	params := Params{
		DashboardID:        738,
		DashboardTitle:     "Autohost Worker",
		DashboardUID:       "ijidtw_Zk",
		DataSourceVariable: "$account",
		DataSourceRegex:    "/^twitch-ce-(dev|aws)",
		MainLoopOps: []Operation{
			{
				Name: "GetAutohostData",
				Dependencies: []string{
					"dynamodb:Scan",
				},
			}, {
				Name: "GetTeamData",
				Dependencies: []string{
					"dynamodb:Scan",
				},
			}, {
				Name: "GetAllHostMap",
				Dependencies: []string{
					"dynamodb:Scan",
				},
			}, {
				Name: "GetLiveChannelData",
				Dependencies: []string{
					"twitchclient/Users:GetUsers",
				},
			},
			{
				Name: "GetActions",
				Dependencies: []string{
					"dynamodb:BatchWriteItem",
					"twitchclient/Roster:GetV1ChannelMemberships",
					"twitchclient/Roster:GetV1TeamMemberships",
				},
			}, {
				Name: "GetRecentStreamDowns",
				Dependencies: []string{
					"twitchclient/Users:GetUsers",
				},
			}, {
				Name: "GetLiveChannelDataForPerformAction",
				Dependencies: []string{
					"twitchclient/Users:GetUsers",
				},
			}, {
				Name: "PerformActions",
				Dependencies: []string{
					"dynamodb:UpdateItem",
					"twitchclient/Clue:Host",
					"twitchclient/Clue:Unhost",
				},
			},
		},
		UpdateLiveChannelsOp: Operation{
			Name: "UpdateLiveChannels",
			Dependencies: []string{
				"dynamodb:BatchWriteItem",
				"dynamodb:UpdateItem",
				"twitchclient/Clue:GetHosts",
				"twitchclient/Clue:Unhost",
				"twitchclient/Users:GetUsers",
				"video.multiplex.internal.multiplexv2/MultiplexV2:ChannelPropertiesLiveHLSChannels",
			},
		},
		RegionVariable:   "$region",
		Service:          "Autohost Worker",
		StageVariable:    "$stage",
		SubstageVariable: "$substage",
		Namespace:        "Autohost Worker",
	}

	panelGroup, err := createPanelGroup(params)
	if err != nil {
		return "", err
	}

	panelsJson, err := panelGroup.ToJson()
	if err != nil {
		return "", err
	}

	dashboard := &Dashboard{
		ID:              params.DashboardID,
		Title:           params.DashboardTitle,
		UID:             params.DashboardUID,
		Panels:          panelsJson,
		Service:         params.Service,
		DataSourceRegex: params.DataSourceRegex,
	}
	dashTemp, err := template.New("DashboardPanelTemplate").Parse(grafana.DashboardTemplate)
	if err != nil {
		return "", err
	}

	var b bytes.Buffer
	err = dashTemp.Execute(&b, dashboard)
	if err != nil {
		return "", err
	}

	return b.String(), nil
}

func createPanelGroup(params Params) (*grafana.PanelGroup, error) {
	panelGroup := grafana.NewPanelGroup()

	addOverviewPanels(params, &panelGroup)
	addDetailPanels(params, &panelGroup, params.MainLoopOps, "Main Loop Details")
	addDetailPanels(params, &panelGroup, []Operation{params.UpdateLiveChannelsOp}, "Update Live Channels Loop Details")

	return &panelGroup, nil
}

func addOverviewPanels(params Params, panelGroup *grafana.PanelGroup) {
	operations := []struct {
		OperationName  string
		DependencyName string
		Title          string
	}{
		{
			OperationName:  "UpdateLiveChannels",
			DependencyName: "twitchclient/Clue:Unhost",
			Title:          "Stream-ups Clue:Unhost Total Requests",
		}, {
			OperationName:  "PerformActions",
			DependencyName: "twitchclient/Clue:Host",
			Title:          "Auto-host Clue:Host Total Requests",
		}, {
			OperationName:  "PerformActions",
			DependencyName: "twitchclient/Clue:Unhost",
			Title:          "Auto-host Clue:Unhost Total Requests",
		},
	}

	depArgs := convertToDependencyPanelArgs(params)
	depArgs.W = 8
	depArgs.H = 9

	panelGroup.AddRow("Overview", false)
	for _, op := range operations {
		depArgs.Operation = op.OperationName
		depArgs.Title = op.Title
		depArgs.Dependency = op.DependencyName

		panel := grafana.NewDependencyTotalPanel(depArgs)
		panelGroup.AddPanel(panel)
	}

	mainLoopPanel := newMainLoopLatencyPanel(params)
	panelGroup.AddPanel(mainLoopPanel)

}

func newMainLoopLatencyPanel(params Params) grafana.GrafanaGraphPanel {
	p := grafana.NewGrafanaGraphPanel()

	targets := make([]grafana.GrafanaTarget, 0, len(params.MainLoopOps))
	for _, op := range params.MainLoopOps {
		target := grafana.GrafanaTarget{
			Alias: op.Name,
			Dimensions: map[string]string{
				"Operation": op.Name,
				"Region":    params.RegionVariable,
				"Service":   params.Service,
				"Stage":     params.StageVariable,
				"Substage":  params.SubstageVariable,
			},
			MatchExact: true,
			MetricName: "Duration",
			Namespace:  params.Namespace,
			Region:     params.RegionVariable,
			Statistics: []string{
				grafana.StatAverage,
			},
		}
		targets = append(targets, target)
	}

	p.DataSource = grafana.StrPtr(params.DataSourceVariable)
	p.GridPos = grafana.GrafanaGridPos{
		H: 8,
		W: grafana.DashboardWidth,
	}
	p.Legend = grafana.GrafanaLegend{
		AlignAsTable: true,
		Avg:          true,
		Show:         true,
		Values:       true,
		RightSide:    grafana.BoolPtr(true),
	}
	p.Points = true
	p.PointRadius = 3
	p.Targets = targets
	p.Title = "Main Loop Latencies"

	return p
}

func addDetailPanels(
	params Params,
	panelGroup *grafana.PanelGroup,
	operations []Operation,
	title string) {

	rowID := panelGroup.AddRow(title, true)

	for _, op := range operations {
		if len(op.Dependencies) == 0 {
			continue
		}

		textPanel := grafana.NewTextPanel(grafana.TextPanelArgs{
			Title:       fmt.Sprintf("%s Dependencies", op.Name),
			Transparent: true,
			W:           grafana.DashboardWidth,
			H:           1,
		})
		panelGroup.AddPanelToNextLineInCollapsedRow(&textPanel, rowID)

		depArgs := grafana.DependencyPanelArgs{
			DataSource: params.DataSourceVariable,
			Namespace:  params.Namespace,
			Operation:  op.Name,
			Region:     params.RegionVariable,
			Service:    params.Service,
			Stage:      params.StageVariable,
			Substage:   params.SubstageVariable,
			W:          grafana.GraphWidthQuarter,
			H:          grafana.GraphHeight,
		}
		for _, dep := range op.Dependencies {
			depArgs.Dependency = dep
			depArgs.Title = fmt.Sprintf("%s Throughput", dep)
			throughputPanel := grafana.NewDependencyThroughputPanel(depArgs)
			panelGroup.AddPanelToCollapsedRow(&throughputPanel, rowID)

			depArgs.Title = fmt.Sprintf("%s Latency", dep)
			depLatencyPanel := grafana.NewDependencyLatencyPanel(depArgs)
			panelGroup.AddPanelToCollapsedRow(&depLatencyPanel, rowID)
		}
	}
}

func convertToDependencyPanelArgs(params Params) grafana.DependencyPanelArgs {
	return grafana.DependencyPanelArgs{
		DataSource: params.DataSourceVariable,
		Namespace:  params.Namespace,
		Region:     params.RegionVariable,
		Service:    params.Service,
		Stage:      params.StageVariable,
		Substage:   params.SubstageVariable,
	}
}
