package grafana

import (
	"context"
	"encoding/json"
	"fmt"
	"net/url"
	"strconv"
	"time"
)

// DefaultFolderId is the id of the general folder
// that is pre-created and cannot be removed.
const DefaultFolderId = 0

// BoardProperties keeps metadata of a dashboard.
type BoardProperties struct {
	IsStarred  bool      `json:"isStarred,omitempty"`
	IsHome     bool      `json:"isHome,omitempty"`
	IsSnapshot bool      `json:"isSnapshot,omitempty"`
	Type       string    `json:"type,omitempty"`
	CanSave    bool      `json:"canSave"`
	CanEdit    bool      `json:"canEdit"`
	CanStar    bool      `json:"canStar"`
	Slug       string    `json:"slug"`
	Expires    time.Time `json:"expires"`
	Created    time.Time `json:"created"`
	Updated    time.Time `json:"updated"`
	UpdatedBy  string    `json:"updatedBy"`
	CreatedBy  string    `json:"createdBy"`
	Version    int       `json:"version"`
}

// FoundBoard keeps result of search with metadata of a dashboard.
type FoundBoard struct {
	ID        uint     `json:"id"`
	UID       string   `json:"uid"`
	Title     string   `json:"title"`
	URI       string   `json:"uri"`
	Type      string   `json:"type"`
	Tags      []string `json:"tags"`
	IsStarred bool     `json:"isStarred"`
	URL       string   `json:"url"`
}

// SetDashboardParams contains the extra parameters
// that affects where and how the dashboard will be stored
type SetDashboardParams struct {
	FolderID  int
	Overwrite bool
}

type (
	// SearchParam is a type for specifying Search params.
	SearchParam func(*url.Values)
	// SearchParamType is a type accepted by SearchType func.
	SearchParamType string
)

// Search entities to be used with SearchType().
const (
	SearchTypeFolder    SearchParamType = "dash-folder"
	SearchTypeDashboard SearchParamType = "dash-db"
)

// SearchQuery specifies Search search query.
// Empty query is silently ignored.
// Specifying it multiple times is futile, only last one will be sent.
func SearchQuery(query string) SearchParam {
	return func(v *url.Values) {
		if query != "" {
			v.Set("query", query)
		}
	}
}

// SearchTag specifies Search tag to search for.
// Empty tag is silently ignored.
// Can be specified multiple times, logical OR is applied.
func SearchTag(tag string) SearchParam {
	return func(v *url.Values) {
		if tag != "" {
			v.Add("tag", tag)
		}
	}
}

// SearchType specifies Search type to search for.
// Specifying it multiple times is futile, only last one will be sent.
func SearchType(searchType SearchParamType) SearchParam {
	return func(v *url.Values) {
		v.Set("type", string(searchType))
	}
}

// SearchDashboardID specifies Search dashboard id's to search for.
// Can be specified multiple times, logical OR is applied.
func SearchDashboardID(dashboardID int) SearchParam {
	return func(v *url.Values) {
		v.Add("dashboardIds", strconv.Itoa(dashboardID))
	}
}

// SearchFolderID specifies Search folder id's to search for.
// Can be specified multiple times, logical OR is applied.
func SearchFolderID(folderID int) SearchParam {
	return func(v *url.Values) {
		v.Add("folderIds", strconv.Itoa(folderID))
	}
}

// SearchStarred specifies if Search should search for starred dashboards only.
// Specifying it multiple times is futile, only last one will be sent.
func SearchStarred(starred bool) SearchParam {
	return func(v *url.Values) {
		v.Set("starred", strconv.FormatBool(starred))
	}
}

// SearchLimit specifies maximum number of results from Search query.
// As of grafana 6.7 it has to be <= 5000. 0 stands for absence of parameter in a query.
// Specifying it multiple times is futile, only last one will be sent.
func SearchLimit(limit uint) SearchParam {
	return func(v *url.Values) {
		if limit > 0 {
			v.Set("limit", strconv.FormatUint(uint64(limit), 10))
		}
	}
}

// SearchPage specifies Search page number to be queried for.
// Zero page is silently ignored, page numbers start from one.
// Specifying it multiple times is futile, only last one will be sent.
func SearchPage(page uint) SearchParam {
	return func(v *url.Values) {
		if page > 0 {
			v.Set("page", strconv.FormatUint(uint64(page), 10))
		}
	}
}

// Search searches folders and dashboards with query params specified.
//
// Reflects GET /api/search API call.
func (r *Client) Search(ctx context.Context, params ...SearchParam) ([]FoundBoard, error) {
	var (
		raw    []byte
		boards []FoundBoard
		code   int
		err    error
	)
	u := url.URL{}
	q := u.Query()
	for _, p := range params {
		p(&q)
	}
	if raw, code, err = r.get(ctx, "api/search", q); err != nil {
		return nil, err
	}
	if code != 200 {
		return nil, fmt.Errorf("HTTP error %d: returns %s", code, raw)
	}
	err = json.Unmarshal(raw, &boards)
	return boards, err
}
