package autohost_grafana

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/pkg/errors"
)

const (
	dataSourceVariable = "$account"
	regionVariable     = "$region"
	stageVariable      = "$stage"
	subStageVariable   = "$substage"
)

type DashboardBuilder struct {
	PanelGroup *PanelGroup
	Namespace  string
	Service    string

	DashboardID     int
	DashboardTitle  string
	DashboardUID    string
	DataSourceRegex string

	Stages    []string
	SubStages []string
}

type DashboardBuilderArgs struct {
	Namespace string
	Service   string

	DashboardID     int
	DashboardTitle  string
	DashboardUID    string
	DataSourceRegex string

	Stages    []string
	SubStages []string
}

type Operation struct {
	Name         string
	Dependencies []Dependency
}

type Dependency struct {
	Name        string
	CircuitName string
}

func NewDashboardBuilder(args *DashboardBuilderArgs) *DashboardBuilder {
	pg := NewPanelGroup()
	return &DashboardBuilder{
		PanelGroup:      &pg,
		Namespace:       args.Namespace,
		Service:         args.Service,
		DashboardID:     args.DashboardID,
		DashboardTitle:  args.DashboardTitle,
		DashboardUID:    args.DashboardUID,
		DataSourceRegex: args.DataSourceRegex,
		Stages:          args.Stages,
		SubStages:       args.SubStages,
	}
}

func (b *DashboardBuilder) AddRow(title string, collapsed bool) int {
	return b.PanelGroup.AddRow(title, collapsed)
}

func (b *DashboardBuilder) AddTitlePanel(title string) {
	b.PanelGroup.AddRow("", false)

	textPanel := NewTextPanel(TextPanelArgs{
		W:           DashboardWidth,
		H:           2,
		Content:     fmt.Sprintf("## %s", title),
		Transparent: true,
	})
	b.PanelGroup.AddPanel(&textPanel)
}

func (b *DashboardBuilder) AddErrorSummaryPanels(
	operations []Operation,
	title string) {

	textPanel := NewTextPanel(TextPanelArgs{
		Title:       title,
		Transparent: true,
		W:           DashboardWidth,
		H:           1,
	})
	b.PanelGroup.AddPanelToNextLine(&textPanel)

	opPanelArgs := b.convertToOperationPanelArgs()
	opPanelArgs.W = 4
	opPanelArgs.H = 5

	for _, op := range operations {
		opPanelArgs.Operation = op.Name
		summaryPanel := NewOperationErrorSummaryPanel(opPanelArgs)
		b.PanelGroup.AddPanel(&summaryPanel)
	}
}

func (b *DashboardBuilder) AddLatencySummaryPanels(
	operations []Operation,
	rowID int,
	title string) {

	textPanel := NewTextPanel(TextPanelArgs{
		Title:       title,
		Transparent: true,
		W:           DashboardWidth,
		H:           1,
	})
	b.PanelGroup.AddPanelToNextLineInCollapsedRow(&textPanel, rowID)

	opPanelArgs := b.convertToOperationPanelArgs()
	opPanelArgs.W = 4
	opPanelArgs.H = 5

	for _, op := range operations {
		opPanelArgs.Operation = op.Name
		summaryPanel := NewOperationLatencySummaryPanel(opPanelArgs)
		b.PanelGroup.AddPanelToCollapsedRow(&summaryPanel, rowID)
	}
}

func (b *DashboardBuilder) AddThroughputSummaryPanels(
	operations []Operation,
	rowID int,
	title string) {

	textPanel := NewTextPanel(TextPanelArgs{
		Title:       title,
		Transparent: true,
		W:           DashboardWidth,
		H:           1,
	})
	b.PanelGroup.AddPanelToNextLineInCollapsedRow(&textPanel, rowID)

	opPanelArgs := b.convertToOperationPanelArgs()
	opPanelArgs.W = 4
	opPanelArgs.H = 5

	for _, op := range operations {
		opPanelArgs.Operation = op.Name
		summaryPanel := NewOperationThroughputSummaryPanel(opPanelArgs)
		b.PanelGroup.AddPanelToCollapsedRow(&summaryPanel, rowID)
	}
}

func (b *DashboardBuilder) AddDetailPanels(
	operations []Operation,
	title string) {

	b.AddTitlePanel(title)

	opsPanelArgs := b.convertToOperationPanelArgs()
	opsPanelArgs.W = GraphWidthHalf
	opsPanelArgs.H = GraphHeight

	for _, op := range operations {
		rowID := b.PanelGroup.AddRow(op.Name, true)

		opsPanelArgs.Operation = op.Name
		throughputPanel := NewOperationThroughputPanel(opsPanelArgs)
		b.PanelGroup.AddPanelToCollapsedRow(&throughputPanel, rowID)

		latencyPanel := NewOperationLatencyPanel(opsPanelArgs)
		b.PanelGroup.AddPanelToCollapsedRow(&latencyPanel, rowID)

		if len(op.Dependencies) == 0 {
			continue
		}

		rowID = b.PanelGroup.AddRow(fmt.Sprintf("%s Dependencies", op.Name), true)
		depArgs := DependencyPanelArgs{
			DataSource: dataSourceVariable,
			Namespace:  b.Namespace,
			Operation:  op.Name,
			Region:     regionVariable,
			Service:    b.Service,
			Stage:      stageVariable,
			Substage:   subStageVariable,
			W:          GraphWidthThird,
			H:          GraphHeight,
		}
		for _, dep := range op.Dependencies {
			depArgs.Dependency = dep.Name
			depArgs.Title = fmt.Sprintf("%s Throughput", dep.Name)
			throughputPanel := NewDependencyThroughputPanel(depArgs)
			b.PanelGroup.AddPanelToNextLineInCollapsedRow(&throughputPanel, rowID)

			depArgs.Title = fmt.Sprintf("%s Latency", dep.Name)
			depLatencyPanel := NewDependencyLatencyPanel(depArgs)
			b.PanelGroup.AddPanelToCollapsedRow(&depLatencyPanel, rowID)

			if dep.CircuitName != "" {
				circuitOpenArgs := CircuitOpenPanelArgs{
					DataSource:  dataSourceVariable,
					CircuitName: dep.CircuitName,
					Namespace:   b.Namespace,
					Region:      regionVariable,
					Service:     b.Service,
					Stage:       stageVariable,
					Substage:    subStageVariable,
					Title:       fmt.Sprintf("%s Total Open Circuits", dep.CircuitName),
					W:           GraphWidthThird,
					H:           GraphHeight,
				}
				circuitOpenPanel := NewCircuitOpenPanel(circuitOpenArgs)
				b.PanelGroup.AddPanelToCollapsedRow(&circuitOpenPanel, rowID)
			} else {
				panel := NewPlaceholderPanel(PlaceholderPanelArgs{
					W: GraphWidthThird,
					H: GraphHeight,
				})
				b.PanelGroup.AddPanelToCollapsedRow(panel, rowID)
			}
		}
	}
}


func (b *DashboardBuilder) AddDependencyDetailPanels(dependencies []Dependency) {
	depArgs := DependencyPanelArgs{
		DataSource: dataSourceVariable,
		Namespace:  b.Namespace,
		Region:     regionVariable,
		Service:    b.Service,
		Stage:      stageVariable,
		Substage:   subStageVariable,
		W:          GraphWidthQuarter,
		H:          GraphHeight,
	}

	rowID := b.PanelGroup.AddRow("Dependency Overview", true)

	for _, dep := range dependencies {
		depArgs.Dependency = dep.Name
		depArgs.Title = fmt.Sprintf("%s Throughput", dep.Name)
		throughputPanel := NewDependencyThroughputPanel(depArgs)
		b.PanelGroup.AddPanelToNextLineInCollapsedRow(&throughputPanel, rowID)

		depArgs.Title = fmt.Sprintf("%s Latency", dep.Name)
		depLatencyPanel := NewDependencyLatencyPanel(depArgs)
		b.PanelGroup.AddPanelToCollapsedRow(&depLatencyPanel, rowID)

		if dep.CircuitName != "" {
			circuitOpenArgs := CircuitOpenPanelArgs{
				DataSource:  dataSourceVariable,
				CircuitName: dep.CircuitName,
				Namespace:   b.Namespace,
				Region:      regionVariable,
				Service:     b.Service,
				Stage:       stageVariable,
				Substage:    subStageVariable,
				Title:       fmt.Sprintf("%s Total Open Circuits", dep.CircuitName),
				W:           GraphWidthQuarter,
				H:           GraphHeight,
			}
			circuitOpenPanel := NewCircuitOpenPanel(circuitOpenArgs)
			b.PanelGroup.AddPanelToCollapsedRow(&circuitOpenPanel, rowID)

			circuitThroughputPanelArgs := CircuitThroughputPanelArgs{
				DataSource:  dataSourceVariable,
				CircuitName: dep.CircuitName,
				Namespace:   b.Namespace,
				Region:      regionVariable,
				Service:     b.Service,
				Stage:       stageVariable,
				Substage:    subStageVariable,
				Title:       fmt.Sprintf("%s Circuit Throughput", dep.CircuitName),
				W:           GraphWidthQuarter,
				H:           GraphHeight,
			}
			circuitThroughputPanel := NewCircuitThroughputPanel(circuitThroughputPanelArgs)
			b.PanelGroup.AddPanelToCollapsedRow(&circuitThroughputPanel, rowID)

		} else {
			placeholderPanel := NewPlaceholderPanel(PlaceholderPanelArgs{
				W: GraphWidthHalf,
				H: GraphHeight,
			})
			b.PanelGroup.AddPanelToCollapsedRow(placeholderPanel, rowID)
		}
	}
}


func (b *DashboardBuilder) convertToOperationPanelArgs() OperationPanelArgs {
	return OperationPanelArgs{
		DataSource: dataSourceVariable,
		Namespace:  b.Namespace,
		Region:     regionVariable,
		Service:    b.Service,
		Stage:      stageVariable,
		Substage:   subStageVariable,
		W:          4,
		H:          5,
	}
}

func (b *DashboardBuilder) ToFile(filePath string) error {
	panelsJson, err := b.PanelGroup.ToJson()
	if err != nil {
		return err
	}

	stageVariableOption, err := getCustomGrafanaVariable(
		"stage", b.Stages)
	if err != nil {
		return err
	}

	subStageVariableOption, err := getCustomGrafanaVariable(
		"substage", b.SubStages)
	if err != nil {
		return err
	}

	templateParams := &DashboardTemplateParams{
		ID:               b.DashboardID,
		Title:            b.DashboardTitle,
		UID:              b.DashboardUID,
		Panels:           panelsJson,
		DataSourceRegex:  b.DataSourceRegex,
		StageVariable:    stageVariableOption,
		SubStageVariable: subStageVariableOption,
	}
	return WriteDashboardToFile(filePath, templateParams)
}

func getCustomGrafanaVariable(name string, values []string) (string, error) {
	options := make([]GrafanaVariableOption, len(values))
	for i, value := range values {
		selected := false
		if i == 0 {
			selected = true
		}

		options[i] = GrafanaVariableOption{
			Selected: selected,
			Text:     value,
			Value:    value,
		}
	}

	grafanaVariable := CustomGrafanaVariable{
		AllValue:    nil,
		Current:     options[0],
		Hide:        0,
		IncludeAll:  false,
		Label:       nil,
		Multi:       false,
		Name:        name,
		Options:     options,
		Query:       strings.Join(values, ", "),
		SkipUrlSync: false,
		Type:        "custom",
	}

	jsonBytes, err := json.MarshalIndent(grafanaVariable, "      ", "  ")
	if err != nil {
		return "", errors.Wrap(err, "converting to json failed")
	}
	return string(jsonBytes), nil
}
