package plugins

import (
	"encoding/json"
	"fmt"
	"regexp"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/components/simplejson"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/services/dashboards"
)

var varRegex = regexp.MustCompile(`(\$\{.+?\})`)

type ImportDashboardCommand struct {
	Dashboard *simplejson.Json
	Path      string
	Inputs    []ImportDashboardInput
	Overwrite bool
	FolderId  int64

	OrgId    int64
	User     *models.SignedInUser
	PluginId string
	Result   *PluginDashboardInfoDTO
}

type ImportDashboardInput struct {
	Type     string `json:"type"`
	PluginId string `json:"pluginId"`
	Name     string `json:"name"`
	Value    string `json:"value"`
}

type DashboardInputMissingError struct {
	VariableName string
}

func (e DashboardInputMissingError) Error() string {
	return fmt.Sprintf("Dashboard input variable: %v missing from import command", e.VariableName)
}

func init() {
	bus.AddHandler("plugins", ImportDashboard)
}

func ImportDashboard(cmd *ImportDashboardCommand) error {
	var dashboard *models.Dashboard
	var err error

	if cmd.PluginId != "" {
		if dashboard, err = loadPluginDashboard(cmd.PluginId, cmd.Path); err != nil {
			return err
		}
	} else {
		dashboard = models.NewDashboardFromJson(cmd.Dashboard)
	}

	evaluator := &DashTemplateEvaluator{
		template: dashboard.Data,
		inputs:   cmd.Inputs,
	}

	generatedDash, err := evaluator.Eval()
	if err != nil {
		return err
	}

	saveCmd := models.SaveDashboardCommand{
		Dashboard: generatedDash,
		OrgId:     cmd.OrgId,
		UserId:    cmd.User.UserId,
		Overwrite: cmd.Overwrite,
		PluginId:  cmd.PluginId,
		FolderId:  cmd.FolderId,
	}

	dto := &dashboards.SaveDashboardDTO{
		OrgId:     cmd.OrgId,
		Dashboard: saveCmd.GetDashboardModel(),
		Overwrite: saveCmd.Overwrite,
		User:      cmd.User,
	}

	savedDash, err := dashboards.NewService().ImportDashboard(dto)

	if err != nil {
		return err
	}

	cmd.Result = &PluginDashboardInfoDTO{
		PluginId:         cmd.PluginId,
		Title:            savedDash.Title,
		Path:             cmd.Path,
		Revision:         savedDash.Data.Get("revision").MustInt64(1),
		FolderId:         savedDash.FolderId,
		ImportedUri:      "db/" + savedDash.Slug,
		ImportedUrl:      savedDash.GetUrl(),
		ImportedRevision: dashboard.Data.Get("revision").MustInt64(1),
		Imported:         true,
		DashboardId:      savedDash.Id,
		Slug:             savedDash.Slug,
	}

	return nil
}

type DashTemplateEvaluator struct {
	template  *simplejson.Json
	inputs    []ImportDashboardInput
	variables map[string]string
	result    *simplejson.Json
}

func (e *DashTemplateEvaluator) findInput(varName string, varType string) *ImportDashboardInput {
	for _, input := range e.inputs {
		if varType == input.Type && (input.Name == varName || input.Name == "*") {
			return &input
		}
	}

	return nil
}

func (e *DashTemplateEvaluator) Eval() (*simplejson.Json, error) {
	e.result = simplejson.New()
	e.variables = make(map[string]string)

	// check that we have all inputs we need
	for _, inputDef := range e.template.Get("__inputs").MustArray() {
		inputDefJson := simplejson.NewFromAny(inputDef)
		inputName := inputDefJson.Get("name").MustString()
		inputType := inputDefJson.Get("type").MustString()
		input := e.findInput(inputName, inputType)

		if input == nil {
			return nil, &DashboardInputMissingError{VariableName: inputName}
		}

		e.variables["${"+inputName+"}"] = input.Value
	}

	return simplejson.NewFromAny(e.evalObject(e.template)), nil
}

func (e *DashTemplateEvaluator) evalValue(source *simplejson.Json) interface{} {
	sourceValue := source.Interface()

	switch v := sourceValue.(type) {
	case string:
		interpolated := varRegex.ReplaceAllStringFunc(v, func(match string) string {
			replacement, exists := e.variables[match]
			if exists {
				return replacement
			}
			return match
		})
		return interpolated
	case bool:
		return v
	case json.Number:
		return v
	case map[string]interface{}:
		return e.evalObject(source)
	case []interface{}:
		array := make([]interface{}, 0)
		for _, item := range v {
			array = append(array, e.evalValue(simplejson.NewFromAny(item)))
		}
		return array
	}

	return nil
}

func (e *DashTemplateEvaluator) evalObject(source *simplejson.Json) interface{} {
	result := make(map[string]interface{})

	for key, value := range source.MustMap() {
		if key == "__inputs" {
			continue
		}
		result[key] = e.evalValue(simplejson.NewFromAny(value))
	}

	return result
}
