// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Dashboard API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/dashboard

package api

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

	"github.com/circonus-labs/circonus-gometrics/api/config"
)

// DashboardGridLayout defines layout
type DashboardGridLayout struct {
	Height uint `json:"height"`
	Width  uint `json:"width"`
}

// DashboardAccessConfig defines access config
type DashboardAccessConfig struct {
	BlackDash           bool   `json:"black_dash"`
	Enabled             bool   `json:"enabled"`
	Fullscreen          bool   `json:"fullscreen"`
	FullscreenHideTitle bool   `json:"fullscreen_hide_title"`
	Nickname            string `json:"nickname"`
	ScaleText           bool   `json:"scale_text"`
	SharedID            string `json:"shared_id"`
	TextSize            uint   `json:"text_size"`
}

// DashboardOptions defines options
type DashboardOptions struct {
	AccessConfigs       []DashboardAccessConfig `json:"access_configs"`
	FullscreenHideTitle bool                    `json:"fullscreen_hide_title"`
	HideGrid            bool                    `json:"hide_grid"`
	Linkages            [][]string              `json:"linkages"`
	ScaleText           bool                    `json:"scale_text"`
	TextSize            uint                    `json:"text_size"`
}

// ChartTextWidgetDatapoint defines datapoints for charts
type ChartTextWidgetDatapoint struct {
	AccountID    string `json:"account_id,omitempty"`     // metric cluster, metric
	CheckID      uint   `json:"_check_id,omitempty"`      // metric
	ClusterID    uint   `json:"cluster_id,omitempty"`     // metric cluster
	ClusterTitle string `json:"_cluster_title,omitempty"` // metric cluster
	Label        string `json:"label,omitempty"`          // metric
	Label2       string `json:"_label,omitempty"`         // metric cluster
	Metric       string `json:"metric,omitempty"`         // metric
	MetricType   string `json:"_metric_type,omitempty"`   // metric
	NumericOnly  bool   `json:"numeric_only,omitempty"`   // metric cluster
}

// ChartWidgetDefinitionLegend defines chart widget definition legend
type ChartWidgetDefinitionLegend struct {
	Show bool   `json:"show,omitempty"`
	Type string `json:"type,omitempty"`
}

// ChartWidgetWedgeLabels defines chart widget wedge labels
type ChartWidgetWedgeLabels struct {
	OnChart  bool `json:"on_chart,omitempty"`
	ToolTips bool `json:"tooltips,omitempty"`
}

// ChartWidgetWedgeValues defines chart widget wedge values
type ChartWidgetWedgeValues struct {
	Angle string `json:"angle,omitempty"`
	Color string `json:"color,omitempty"`
	Show  bool   `json:"show,omitempty"`
}

// ChartWidgtDefinition defines chart widget definition
type ChartWidgtDefinition struct {
	Datasource        string                      `json:"datasource,omitempty"`
	Derive            string                      `json:"derive,omitempty"`
	DisableAutoformat bool                        `json:"disable_autoformat,omitempty"`
	Formula           string                      `json:"formula,omitempty"`
	Legend            ChartWidgetDefinitionLegend `json:"legend,omitempty"`
	Period            uint                        `json:"period,omitempty"`
	PopOnHover        bool                        `json:"pop_onhover,omitempty"`
	WedgeLabels       ChartWidgetWedgeLabels      `json:"wedge_labels,omitempty"`
	WedgeValues       ChartWidgetWedgeValues      `json:"wedge_values,omitempty"`
}

// ForecastGaugeWidgetThresholds defines forecast widget thresholds
type ForecastGaugeWidgetThresholds struct {
	Colors []string `json:"colors,omitempty"` // forecasts, gauges
	Flip   bool     `json:"flip,omitempty"`   // gauges
	Values []string `json:"values,omitempty"` // forecasts, gauges
}

// StatusWidgetAgentStatusSettings defines agent status settings
type StatusWidgetAgentStatusSettings struct {
	Search         string `json:"search,omitempty"`
	ShowAgentTypes string `json:"show_agent_types,omitempty"`
	ShowContact    bool   `json:"show_contact,omitempty"`
	ShowFeeds      bool   `json:"show_feeds,omitempty"`
	ShowSetup      bool   `json:"show_setup,omitempty"`
	ShowSkew       bool   `json:"show_skew,omitempty"`
	ShowUpdates    bool   `json:"show_updates,omitempty"`
}

// StatusWidgetHostStatusSettings defines host status settings
type StatusWidgetHostStatusSettings struct {
	LayoutStyle  string   `json:"layout_style,omitempty"`
	Search       string   `json:"search,omitempty"`
	SortBy       string   `json:"sort_by,omitempty"`
	TagFilterSet []string `json:"tag_filter_set,omitempty"`
}

// DashboardWidgetSettings defines settings specific to widget
// Note: optional attributes which are structs need to be pointers so they will be omitted
type DashboardWidgetSettings struct {
	AccountID           string                           `json:"account_id,omitempty"`            // alerts, clusters, gauges, graphs, lists, status
	Acknowledged        string                           `json:"acknowledged,omitempty"`          // alerts
	AgentStatusSettings *StatusWidgetAgentStatusSettings `json:"agent_status_settings,omitempty"` // status
	Algorithm           string                           `json:"algorithm,omitempty"`             // clusters
	Autoformat          bool                             `json:"autoformat,omitempty"`            // text
	BodyFormat          string                           `json:"body_format,omitempty"`           // text
	ChartType           string                           `json:"chart_type,omitempty"`            // charts
	CheckUUID           string                           `json:"check_uuid,omitempty"`            // gauges
	Cleared             string                           `json:"cleared,omitempty"`               // alerts
	ClusterID           uint                             `json:"cluster_id,omitempty"`            // clusters
	ClusterName         string                           `json:"cluster_name,omitempty"`          // clusters
	ContactGroups       []uint                           `json:"contact_groups,omitempty"`        // alerts
	ContentType         string                           `json:"content_type,omitempty"`          // status
	Datapoints          []ChartTextWidgetDatapoint       `json:"datapoints,omitempty"`            // charts, text
	DateWindow          string                           `json:"date_window,omitempty"`           // graphs
	Definition          *ChartWidgtDefinition            `json:"definition,omitempty"`            // charts
	Dependents          string                           `json:"dependents,omitempty"`            // alerts
	DisableAutoformat   bool                             `json:"disable_autoformat,omitempty"`    // gauges
	Display             string                           `json:"display,omitempty"`               // alerts
	Format              string                           `json:"format,omitempty"`                // forecasts
	Formula             string                           `json:"formula,omitempty"`               // gauges
	GraphUUID           string                           `json:"graph_id,omitempty"`              // graphs
	HideXAxis           bool                             `json:"hide_xaxis,omitempty"`            // graphs
	HideYAxis           bool                             `json:"hide_yaxis,omitempty"`            // graphs
	HostStatusSettings  *StatusWidgetHostStatusSettings  `json:"host_status_settings,omitempty"`  // status
	KeyInline           bool                             `json:"key_inline,omitempty"`            // graphs
	KeyLoc              string                           `json:"key_loc,omitempty"`               // graphs
	KeySize             uint                             `json:"key_size,omitempty"`              // graphs
	KeyWrap             bool                             `json:"key_wrap,omitempty"`              // graphs
	Label               string                           `json:"label,omitempty"`                 // graphs
	Layout              string                           `json:"layout,omitempty"`                // clusters
	Limit               uint                             `json:"limit,omitempty"`                 // lists
	Maintenance         string                           `json:"maintenance,omitempty"`           // alerts
	Markup              string                           `json:"markup,omitempty"`                // html
	MetricDisplayName   string                           `json:"metric_display_name,omitempty"`   // gauges
	MetricName          string                           `json:"metric_name,omitempty"`           // gauges
	MinAge              string                           `json:"min_age,omitempty"`               // alerts
	OffHours            []uint                           `json:"off_hours,omitempty"`             // alerts
	OverlaySetID        string                           `json:"overlay_set_id,omitempty"`        // graphs
	Period              uint                             `json:"period,omitempty"`                // gauges, text, graphs
	RangeHigh           int                              `json:"range_high,omitempty"`            // gauges
	RangeLow            int                              `json:"range_low,omitempty"`             // gauges
	Realtime            bool                             `json:"realtime,omitempty"`              // graphs
	ResourceLimit       string                           `json:"resource_limit,omitempty"`        // forecasts
	ResourceUsage       string                           `json:"resource_usage,omitempty"`        // forecasts
	Search              string                           `json:"search,omitempty"`                // alerts, lists
	Severity            string                           `json:"severity,omitempty"`              // alerts
	ShowFlags           bool                             `json:"show_flags,omitempty"`            // graphs
	Size                string                           `json:"size,omitempty"`                  // clusters
	TagFilterSet        []string                         `json:"tag_filter_set,omitempty"`        // alerts
	Threshold           float32                          `json:"threshold,omitempty"`             // clusters
	Thresholds          *ForecastGaugeWidgetThresholds   `json:"thresholds,omitempty"`            // forecasts, gauges
	TimeWindow          string                           `json:"time_window,omitempty"`           // alerts
	Title               string                           `json:"title,omitempty"`                 // alerts, charts, forecasts, gauges, html
	TitleFormat         string                           `json:"title_format,omitempty"`          // text
	Trend               string                           `json:"trend,omitempty"`                 // forecasts
	Type                string                           `json:"type,omitempty"`                  // gauges, lists
	UseDefault          bool                             `json:"use_default,omitempty"`           // text
	ValueType           string                           `json:"value_type,omitempty"`            // gauges, text
	WeekDays            []string                         `json:"weekdays,omitempty"`              // alerts
}

// DashboardWidget defines widget
type DashboardWidget struct {
	Active   bool                    `json:"active"`
	Height   uint                    `json:"height"`
	Name     string                  `json:"name"`
	Origin   string                  `json:"origin"`
	Settings DashboardWidgetSettings `json:"settings"`
	Type     string                  `json:"type"`
	WidgetID string                  `json:"widget_id"`
	Width    uint                    `json:"width"`
}

// Dashboard defines a dashboard. See https://login.circonus.com/resources/api/calls/dashboard for more information.
type Dashboard struct {
	AccountDefault bool                `json:"account_default"`
	Active         bool                `json:"_active,omitempty"`
	CID            string              `json:"_cid,omitempty"`
	Created        uint                `json:"_created,omitempty"`
	CreatedBy      string              `json:"_created_by,omitempty"`
	GridLayout     DashboardGridLayout `json:"grid_layout"`
	LastModified   uint                `json:"_last_modified,omitempty"`
	Options        DashboardOptions    `json:"options"`
	Shared         bool                `json:"shared"`
	Title          string              `json:"title"`
	UUID           string              `json:"_dashboard_uuid,omitempty"`
	Widgets        []DashboardWidget   `json:"widgets"`
}

// NewDashboard returns a new Dashboard (with defaults, if applicable)
func NewDashboard() *Dashboard {
	return &Dashboard{}
}

// FetchDashboard retrieves dashboard with passed cid.
func (a *API) FetchDashboard(cid CIDType) (*Dashboard, error) {
	if cid == nil || *cid == "" {
		return nil, fmt.Errorf("Invalid dashboard CID [none]")
	}

	dashboardCID := string(*cid)

	matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID)
	if err != nil {
		return nil, err
	}
	if !matched {
		return nil, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID)
	}

	result, err := a.Get(string(*cid))
	if err != nil {
		return nil, err
	}

	if a.Debug {
		a.Log.Printf("[DEBUG] fetch dashboard, received JSON: %s", string(result))
	}

	dashboard := new(Dashboard)
	if err := json.Unmarshal(result, dashboard); err != nil {
		return nil, err
	}

	return dashboard, nil
}

// FetchDashboards retrieves all dashboards available to the API Token.
func (a *API) FetchDashboards() (*[]Dashboard, error) {
	result, err := a.Get(config.DashboardPrefix)
	if err != nil {
		return nil, err
	}

	var dashboards []Dashboard
	if err := json.Unmarshal(result, &dashboards); err != nil {
		return nil, err
	}

	return &dashboards, nil
}

// UpdateDashboard updates passed dashboard.
func (a *API) UpdateDashboard(cfg *Dashboard) (*Dashboard, error) {
	if cfg == nil {
		return nil, fmt.Errorf("Invalid dashboard config [nil]")
	}

	dashboardCID := string(cfg.CID)

	matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID)
	if err != nil {
		return nil, err
	}
	if !matched {
		return nil, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID)
	}

	jsonCfg, err := json.Marshal(cfg)
	if err != nil {
		return nil, err
	}

	if a.Debug {
		a.Log.Printf("[DEBUG] update dashboard, sending JSON: %s", string(jsonCfg))
	}

	result, err := a.Put(dashboardCID, jsonCfg)
	if err != nil {
		return nil, err
	}

	dashboard := &Dashboard{}
	if err := json.Unmarshal(result, dashboard); err != nil {
		return nil, err
	}

	return dashboard, nil
}

// CreateDashboard creates a new dashboard.
func (a *API) CreateDashboard(cfg *Dashboard) (*Dashboard, error) {
	if cfg == nil {
		return nil, fmt.Errorf("Invalid dashboard config [nil]")
	}

	jsonCfg, err := json.Marshal(cfg)
	if err != nil {
		return nil, err
	}

	if a.Debug {
		a.Log.Printf("[DEBUG] create dashboard, sending JSON: %s", string(jsonCfg))
	}

	result, err := a.Post(config.DashboardPrefix, jsonCfg)
	if err != nil {
		return nil, err
	}

	dashboard := &Dashboard{}
	if err := json.Unmarshal(result, dashboard); err != nil {
		return nil, err
	}

	return dashboard, nil
}

// DeleteDashboard deletes passed dashboard.
func (a *API) DeleteDashboard(cfg *Dashboard) (bool, error) {
	if cfg == nil {
		return false, fmt.Errorf("Invalid dashboard config [nil]")
	}
	return a.DeleteDashboardByCID(CIDType(&cfg.CID))
}

// DeleteDashboardByCID deletes dashboard with passed cid.
func (a *API) DeleteDashboardByCID(cid CIDType) (bool, error) {
	if cid == nil || *cid == "" {
		return false, fmt.Errorf("Invalid dashboard CID [none]")
	}

	dashboardCID := string(*cid)

	matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID)
	if err != nil {
		return false, err
	}
	if !matched {
		return false, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID)
	}

	_, err = a.Delete(dashboardCID)
	if err != nil {
		return false, err
	}

	return true, nil
}

// SearchDashboards returns dashboards matching the specified
// search query and/or filter. If nil is passed for both parameters
// all dashboards will be returned.
func (a *API) SearchDashboards(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Dashboard, error) {
	q := url.Values{}

	if searchCriteria != nil && *searchCriteria != "" {
		q.Set("search", string(*searchCriteria))
	}

	if filterCriteria != nil && len(*filterCriteria) > 0 {
		for filter, criteria := range *filterCriteria {
			for _, val := range criteria {
				q.Add(filter, val)
			}
		}
	}

	if q.Encode() == "" {
		return a.FetchDashboards()
	}

	reqURL := url.URL{
		Path:     config.DashboardPrefix,
		RawQuery: q.Encode(),
	}

	result, err := a.Get(reqURL.String())
	if err != nil {
		return nil, fmt.Errorf("[ERROR] API call error %+v", err)
	}

	var dashboards []Dashboard
	if err := json.Unmarshal(result, &dashboards); err != nil {
		return nil, err
	}

	return &dashboards, nil
}
