package pagerduty

import (
	"fmt"
	"net/http"

	"github.com/google/go-querystring/query"
)

const (
	escPath = "/escalation_policies"
)

// EscalationRule is a rule for an escalation policy to trigger.
type EscalationRule struct {
	ID      string      `json:"id,omitempty"`
	Delay   uint        `json:"escalation_delay_in_minutes,omitempty"`
	Targets []APIObject `json:"targets"`
}

// EscalationPolicy is a collection of escalation rules.
type EscalationPolicy struct {
	APIObject
	Name            string                   `json:"name,omitempty"`
	EscalationRules []EscalationRule         `json:"escalation_rules,omitempty"`
	Services        []APIObject              `json:"services,omitempty"`
	NumLoops        uint                     `json:"num_loops,omitempty"`
	Teams           []APIReference           `json:"teams"`
	OnCall          []EscalationPolicyOnCall `json:"on_call"`
	Description     string                   `json:"description,omitempty"`
	RepeatEnabled   bool                     `json:"repeat_enabled,omitempty"`
}

//
type EscalationPolicyOnCall struct {
	Level uint   `json:"level"`
	Start string `json:"start,omitempty"`
	End   string `json:"end,omitempty"`
	User  User   `json:"user,omitempty"`
}

// ListEscalationPoliciesResponse is the data structure returned from calling the ListEscalationPolicies API endpoint.
type ListEscalationPoliciesResponse struct {
	APIListObject
	EscalationPolicies []EscalationPolicy `json:"escalation_policies"`
}

type ListEscalationRulesResponse struct {
	APIListObject
	EscalationRules []EscalationRule `json:"escalation_rules"`
}

// ListEscalationPoliciesOptions is the data structure used when calling the ListEscalationPolicies API endpoint.
type ListEscalationPoliciesOptions struct {
	APIListObject
	Query    string   `url:"query,omitempty"`
	UserIDs  []string `url:"user_ids,omitempty,brackets"`
	TeamIDs  []string `url:"team_ids,omitempty,brackets"`
	Includes []string `url:"include,omitempty,brackets"`
	SortBy   string   `url:"sort_by,omitempty"`
}

// GetEscalationRuleOptions is the data structure used when calling the GetEscalationRule API endpoint.
type GetEscalationRuleOptions struct {
	Includes []string `url:"include,omitempty,brackets"`
}

// ListEscalationPolicies lists all of the existing escalation policies.
func (c *Client) ListEscalationPolicies(o ListEscalationPoliciesOptions) (*ListEscalationPoliciesResponse, error) {
	v, err := query.Values(o)
	if err != nil {
		return nil, err
	}
	resp, err := c.get(escPath + "?" + v.Encode())
	if err != nil {
		return nil, err
	}
	var result ListEscalationPoliciesResponse
	return &result, c.decodeJSON(resp, &result)
}

// CreateEscalationPolicy creates a new escalation policy.
func (c *Client) CreateEscalationPolicy(e EscalationPolicy) (*EscalationPolicy, error) {
	data := make(map[string]EscalationPolicy)
	data["escalation_policy"] = e
	resp, err := c.post(escPath, data, nil)
	return getEscalationPolicyFromResponse(c, resp, err)
}

// DeleteEscalationPolicy deletes an existing escalation policy and rules.
func (c *Client) DeleteEscalationPolicy(id string) error {
	_, err := c.delete(escPath + "/" + id)
	return err
}

// GetEscalationPolicyOptions is the data structure used when calling the GetEscalationPolicy API endpoint.
type GetEscalationPolicyOptions struct {
	Includes []string `url:"include,omitempty,brackets"`
}

// GetEscalationPolicy gets information about an existing escalation policy and its rules.
func (c *Client) GetEscalationPolicy(id string, o *GetEscalationPolicyOptions) (*EscalationPolicy, error) {
	v, err := query.Values(o)
	if err != nil {
		return nil, err
	}
	resp, err := c.get(escPath + "/" + id + "?" + v.Encode())
	return getEscalationPolicyFromResponse(c, resp, err)
}

// UpdateEscalationPolicy updates an existing escalation policy and its rules.
func (c *Client) UpdateEscalationPolicy(id string, e *EscalationPolicy) (*EscalationPolicy, error) {
	data := make(map[string]EscalationPolicy)
	data["escalation_policy"] = *e
	resp, err := c.put(escPath+"/"+id, data, nil)
	return getEscalationPolicyFromResponse(c, resp, err)
}

// CreateEscalationRule creates a new escalation rule for an escalation policy
// and appends it to the end of the existing escalation rules.
func (c *Client) CreateEscalationRule(escID string, e EscalationRule) (*EscalationRule, error) {
	data := make(map[string]EscalationRule)
	data["escalation_rule"] = e
	resp, err := c.post(escPath+"/"+escID+"/escalation_rules", data, nil)
	return getEscalationRuleFromResponse(c, resp, err)
}

// GetEscalationRule gets information about an existing escalation rule.
func (c *Client) GetEscalationRule(escID string, id string, o *GetEscalationRuleOptions) (*EscalationRule, error) {
	v, err := query.Values(o)
	if err != nil {
		return nil, err
	}
	resp, err := c.get(escPath + "/" + escID + "/escalation_rules/" + id + "?" + v.Encode())
	return getEscalationRuleFromResponse(c, resp, err)
}

// DeleteEscalationRule deletes an existing escalation rule.
func (c *Client) DeleteEscalationRule(escID string, id string) error {
	_, err := c.delete(escPath + "/" + escID + "/escalation_rules/" + id)
	return err
}

// UpdateEscalationRule updates an existing escalation rule.
func (c *Client) UpdateEscalationRule(escID string, id string, e *EscalationRule) (*EscalationRule, error) {
	data := make(map[string]EscalationRule)
	data["escalation_rule"] = *e
	resp, err := c.put(escPath+"/"+escID+"/escalation_rules/"+id, data, nil)
	return getEscalationRuleFromResponse(c, resp, err)
}

// ListEscalationRules lists all of the escalation rules for an existing escalation policy.
func (c *Client) ListEscalationRules(escID string) (*ListEscalationRulesResponse, error) {
	resp, err := c.get(escPath + "/" + escID + "/escalation_rules")
	if err != nil {
		return nil, err
	}

	var result ListEscalationRulesResponse
	return &result, c.decodeJSON(resp, &result)
}

func getEscalationRuleFromResponse(c *Client, resp *http.Response, err error) (*EscalationRule, error) {
	defer resp.Body.Close()
	if err != nil {
		return nil, err
	}
	var target map[string]EscalationRule
	if dErr := c.decodeJSON(resp, &target); dErr != nil {
		return nil, fmt.Errorf("Could not decode JSON response: %v", dErr)
	}
	rootNode := "escalation_rule"
	t, nodeOK := target[rootNode]
	if !nodeOK {
		return nil, fmt.Errorf("JSON response does not have %s field", rootNode)
	}
	return &t, nil
}

func getEscalationPolicyFromResponse(c *Client, resp *http.Response, err error) (*EscalationPolicy, error) {
	defer resp.Body.Close()
	if err != nil {
		return nil, err
	}
	var target map[string]EscalationPolicy
	if dErr := c.decodeJSON(resp, &target); dErr != nil {
		return nil, fmt.Errorf("Could not decode JSON response: %v", dErr)
	}
	rootNode := "escalation_policy"
	t, nodeOK := target[rootNode]
	if !nodeOK {
		return nil, fmt.Errorf("JSON response does not have %s field", rootNode)
	}
	return &t, nil
}
