/*
 * Datadog API for Go
 *
 * Please see the included LICENSE file for licensing information.
 *
 * Copyright 2013 by authors and contributors.
 */

package datadog

import (
	"encoding/json"
	"fmt"
)

// GraphDefinitionRequestStyle represents the graph style attributes
type GraphDefinitionRequestStyle struct {
	Palette *string `json:"palette,omitempty"`
	Width   *string `json:"width,omitempty"`
	Type    *string `json:"type,omitempty"`
}

// GraphDefinitionRequest represents the requests passed into each graph.
type GraphDefinitionRequest struct {
	Stacked            *bool                        `json:"stacked,omitempty"`
	Aggregator         *string                      `json:"aggregator,omitempty"`
	ConditionalFormats []DashboardConditionalFormat `json:"conditional_formats,omitempty"`
	Type               *string                      `json:"type,omitempty"`
	Style              *GraphDefinitionRequestStyle `json:"style,omitempty"`

	// For change type graphs
	ChangeType     *string                            `json:"change_type,omitempty"`
	OrderDirection *string                            `json:"order_dir,omitempty"`
	CompareTo      *string                            `json:"compare_to,omitempty"`
	IncreaseGood   *bool                              `json:"increase_good,omitempty"`
	OrderBy        *string                            `json:"order_by,omitempty"`
	ExtraCol       *string                            `json:"extra_col,omitempty"`
	Metadata       map[string]GraphDefinitionMetadata `json:"metadata,omitempty"`

	// A Graph can only have one of these types of query.
	Query         *string             `json:"q,omitempty"`
	LogQuery      *GraphApmOrLogQuery `json:"log_query,omitempty"`
	ApmQuery      *GraphApmOrLogQuery `json:"apm_query,omitempty"`
	ProcessQuery  *GraphProcessQuery  `json:"process_query,omitempty"`
	RumQuery      *GraphApmOrLogQuery `json:"rum_query,omitempty"`
	SecurityQuery *GraphApmOrLogQuery `json:"security_query,omitempty"`
}

// GraphApmOrLogQuery represents an APM or a Log query
type GraphApmOrLogQuery struct {
	Index   *string                     `json:"index"`
	Compute *GraphApmOrLogQueryCompute  `json:"compute"`
	Search  *GraphApmOrLogQuerySearch   `json:"search,omitempty"`
	GroupBy []GraphApmOrLogQueryGroupBy `json:"groupBy,omitempty"`
}

type GraphApmOrLogQueryCompute struct {
	Aggregation *string `json:"aggregation"`
	Facet       *string `json:"facet,omitempty"`
	Interval    *int    `json:"interval,omitempty"`
}

type GraphApmOrLogQuerySearch struct {
	Query *string `json:"query"`
}

type GraphApmOrLogQueryGroupBy struct {
	Facet *string                        `json:"facet"`
	Limit *int                           `json:"limit,omitempty"`
	Sort  *GraphApmOrLogQueryGroupBySort `json:"sort,omitempty"`
}

type GraphApmOrLogQueryGroupBySort struct {
	Aggregation *string `json:"aggregation"`
	Order       *string `json:"order"`
	Facet       *string `json:"facet,omitempty"`
}

type GraphProcessQuery struct {
	Metric   *string  `json:"metric"`
	SearchBy *string  `json:"search_by,omitempty"`
	FilterBy []string `json:"filter_by,omitempty"`
	Limit    *int     `json:"limit,omitempty"`
}

type GraphDefinitionMetadata TileDefMetadata

type GraphDefinitionMarker struct {
	Type  *string      `json:"type,omitempty"`
	Value *string      `json:"value,omitempty"`
	Label *string      `json:"label,omitempty"`
	Val   *json.Number `json:"val,omitempty"`
	Min   *json.Number `json:"min,omitempty"`
	Max   *json.Number `json:"max,omitempty"`
}

type GraphEvent struct {
	Query *string `json:"q,omitempty"`
}

type Yaxis struct {
	Min          *float64 `json:"min,omitempty"`
	AutoMin      bool     `json:"-"`
	Max          *float64 `json:"max,omitempty"`
	AutoMax      bool     `json:"-"`
	Scale        *string  `json:"scale,omitempty"`
	IncludeZero  *bool    `json:"includeZero,omitempty"`
	IncludeUnits *bool    `json:"units,omitempty"`
}

// UnmarshalJSON is a Custom Unmarshal for Yaxis.Min/Yaxis.Max. If the datadog API
// returns "auto" for min or max, then we should set Yaxis.min or Yaxis.max to nil,
// respectively.
func (y *Yaxis) UnmarshalJSON(data []byte) error {
	type Alias Yaxis
	wrapper := &struct {
		Min *interface{} `json:"min,omitempty"`
		Max *interface{} `json:"max,omitempty"`
		*Alias
	}{
		Alias: (*Alias)(y),
	}

	if err := json.Unmarshal(data, &wrapper); err != nil {
		return err
	}

	val, auto, err := GetFloatFromInterface(wrapper.Min)
	if err != nil {
		return fmt.Errorf(`faild parsing value for Yaxis.min: %s`, err.Error())
	}
	y.AutoMin = auto
	y.Min = val

	val, auto, err = GetFloatFromInterface(wrapper.Max)
	if err != nil {
		return fmt.Errorf(`faild parsing value for Yaxis.max: %s`, err.Error())
	}
	y.AutoMax = auto
	y.Max = val

	return nil
}

type Style struct {
	Palette     *string      `json:"palette,omitempty"`
	PaletteFlip *bool        `json:"paletteFlip,omitempty"`
	FillMin     *json.Number `json:"fillMin,omitempty"`
	FillMax     *json.Number `json:"fillMax,omitempty"`
}

type GraphDefinition struct {
	Viz      *string                  `json:"viz,omitempty"`
	Requests []GraphDefinitionRequest `json:"requests,omitempty"`
	Events   []GraphEvent             `json:"events,omitempty"`
	Markers  []GraphDefinitionMarker  `json:"markers,omitempty"`

	// For timeseries type graphs
	Yaxis Yaxis `json:"yaxis,omitempty"`

	// For query value type graphs
	Autoscale  *bool       `json:"autoscale,omitempty"`
	TextAlign  *string     `json:"text_align,omitempty"`
	Precision  *PrecisionT `json:"precision,omitempty"`
	CustomUnit *string     `json:"custom_unit,omitempty"`

	// For hostmaps
	Style                 *Style   `json:"style,omitempty"`
	Groups                []string `json:"group,omitempty"`
	IncludeNoMetricHosts  *bool    `json:"noMetricHosts,omitempty"`
	Scopes                []string `json:"scope,omitempty"`
	IncludeUngroupedHosts *bool    `json:"noGroupHosts,omitempty"`
	NodeType              *string  `json:"nodeType,omitempty"`
}

// Graph represents a graph that might exist on a dashboard.
type Graph struct {
	Title      *string          `json:"title,omitempty"`
	Definition *GraphDefinition `json:"definition"`
}

// Template variable represents a template variable that might exist on a dashboard
type TemplateVariable struct {
	Name    *string `json:"name,omitempty"`
	Prefix  *string `json:"prefix,omitempty"`
	Default *string `json:"default,omitempty"`
}

// Dashboard represents a user created dashboard. This is the full dashboard
// struct when we load a dashboard in detail.
type Dashboard struct {
	Id                *int               `json:"id,omitempty"`
	NewId             *string            `json:"new_id,omitempty"`
	Description       *string            `json:"description,omitempty"`
	Title             *string            `json:"title,omitempty"`
	Graphs            []Graph            `json:"graphs,omitempty"`
	TemplateVariables []TemplateVariable `json:"template_variables,omitempty"`
	ReadOnly          *bool              `json:"read_only,omitempty"`
}

// DashboardLite represents a user created dashboard. This is the mini
// struct when we load the summaries.
type DashboardLite struct {
	Id          *int       `json:"id,string,omitempty"` // TODO: Remove ',string'.
	Resource    *string    `json:"resource,omitempty"`
	Description *string    `json:"description,omitempty"`
	Title       *string    `json:"title,omitempty"`
	ReadOnly    *bool      `json:"read_only,omitempty"`
	Created     *string    `json:"created,omitempty"`
	Modified    *string    `json:"modified,omitempty"`
	CreatedBy   *CreatedBy `json:"created_by,omitempty"`
}

// CreatedBy represents a field from DashboardLite.
type CreatedBy struct {
	Disabled   *bool   `json:"disabled,omitempty"`
	Handle     *string `json:"handle,omitempty"`
	Name       *string `json:"name,omitempty"`
	IsAdmin    *bool   `json:"is_admin,omitempty"`
	Role       *string `json:"role,omitempty"`
	AccessRole *string `json:"access_role,omitempty"`
	Verified   *bool   `json:"verified,omitempty"`
	Email      *string `json:"email,omitempty"`
	Icon       *string `json:"icon,omitempty"`
}

// reqGetDashboards from /api/v1/dash
type reqGetDashboards struct {
	Dashboards []DashboardLite `json:"dashes,omitempty"`
}

// reqGetDashboard from /api/v1/dash/:dashboard_id
type reqGetDashboard struct {
	Resource  *string    `json:"resource,omitempty"`
	Url       *string    `json:"url,omitempty"`
	Dashboard *Dashboard `json:"dash,omitempty"`
}

type DashboardConditionalFormat struct {
	Palette        *string      `json:"palette,omitempty"`
	Comparator     *string      `json:"comparator,omitempty"`
	CustomBgColor  *string      `json:"custom_bg_color,omitempty"`
	Value          *json.Number `json:"value,omitempty"`
	Inverted       *bool        `json:"invert,omitempty"`
	CustomFgColor  *string      `json:"custom_fg_color,omitempty"`
	CustomImageUrl *string      `json:"custom_image,omitempty"`
}

// GetDashboard returns a single dashboard created on this account.
func (client *Client) GetDashboard(id interface{}) (*Dashboard, error) {

	stringId, err := GetStringId(id)
	if err != nil {
		return nil, err
	}

	var out reqGetDashboard
	if err := client.doJsonRequest("GET", fmt.Sprintf("/v1/dash/%s", stringId), nil, &out); err != nil {
		return nil, err
	}
	return out.Dashboard, nil
}

// GetDashboards returns a list of all dashboards created on this account.
func (client *Client) GetDashboards() ([]DashboardLite, error) {
	var out reqGetDashboards
	if err := client.doJsonRequest("GET", "/v1/dash", nil, &out); err != nil {
		return nil, err
	}
	return out.Dashboards, nil
}

// DeleteDashboard deletes a dashboard by the identifier.
func (client *Client) DeleteDashboard(id int) error {
	return client.doJsonRequest("DELETE", fmt.Sprintf("/v1/dash/%d", id), nil, nil)
}

// CreateDashboard creates a new dashboard when given a Dashboard struct. Note
// that the Id, Resource, Url and similar elements are not used in creation.
func (client *Client) CreateDashboard(dash *Dashboard) (*Dashboard, error) {
	var out reqGetDashboard
	if err := client.doJsonRequest("POST", "/v1/dash", dash, &out); err != nil {
		return nil, err
	}
	return out.Dashboard, nil
}

// UpdateDashboard in essence takes a Dashboard struct and persists it back to
// the server. Use this if you've updated your local and need to push it back.
func (client *Client) UpdateDashboard(dash *Dashboard) error {
	return client.doJsonRequest("PUT", fmt.Sprintf("/v1/dash/%d", *dash.Id),
		dash, nil)
}
