package pagerduty

import (
	"fmt"
	"net/http"

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

// Restriction limits on-call responsibility for a layer to certain times of the day or week.
type Restriction struct {
	Type            string `json:"type,omitempty"`
	StartTimeOfDay  string `json:"start_time_of_day,omitempty"`
	StartDayOfWeek  uint   `json:"start_day_of_week,omitempty"`
	DurationSeconds uint   `json:"duration_seconds,omitempty"`
}

// RenderedScheduleEntry represents the computed set of schedule layer entries that put users on call for a schedule, and cannot be modified directly.
type RenderedScheduleEntry struct {
	Start string    `json:"start,omitempty"`
	End   string    `json:"end,omitempty"`
	User  APIObject `json:"user,omitempty"`
}

// ScheduleLayer is an entry that puts users on call for a schedule.
type ScheduleLayer struct {
	APIObject
	Name                       string                  `json:"name,omitempty"`
	Start                      string                  `json:"start,omitempty"`
	End                        string                  `json:"end,omitempty"`
	RotationVirtualStart       string                  `json:"rotation_virtual_start,omitempty"`
	RotationTurnLengthSeconds  uint                    `json:"rotation_turn_length_seconds,omitempty"`
	Users                      []UserReference         `json:"users,omitempty"`
	Restrictions               []Restriction           `json:"restrictions,omitempty"`
	RenderedScheduleEntries    []RenderedScheduleEntry `json:"rendered_schedule_entries,omitempty"`
	RenderedCoveragePercentage float64                 `json:"rendered_coverage_percentage,omitempty"`
}

// Schedule determines the time periods that users are on call.
type Schedule struct {
	APIObject
	Name                string          `json:"name,omitempty"`
	TimeZone            string          `json:"time_zone,omitempty"`
	Description         string          `json:"description,omitempty"`
	EscalationPolicies  []APIObject     `json:"escalation_policies,omitempty"`
	Users               []APIObject     `json:"users,omitempty"`
	ScheduleLayers      []ScheduleLayer `json:"schedule_layers,omitempty"`
	OverrideSubschedule ScheduleLayer   `json:"override_subschedule,omitempty"`
	FinalSchedule       ScheduleLayer   `json:"final_schedule,omitempty"`
}

// ListSchedulesOptions is the data structure used when calling the ListSchedules API endpoint.
type ListSchedulesOptions struct {
	APIListObject
	Query string `url:"query,omitempty"`
}

// ListSchedulesResponse is the data structure returned from calling the ListSchedules API endpoint.
type ListSchedulesResponse struct {
	APIListObject
	Schedules []Schedule `json:"schedules"`
}

// UserReference is a reference to an authorized PagerDuty user.
type UserReference struct {
	User APIObject `json:"user"`
}

// ListSchedules lists the on-call schedules.
func (c *Client) ListSchedules(o ListSchedulesOptions) (*ListSchedulesResponse, error) {
	v, err := query.Values(o)
	if err != nil {
		return nil, err
	}
	resp, err := c.get("/schedules?" + v.Encode())
	if err != nil {
		return nil, err
	}
	var result ListSchedulesResponse
	return &result, c.decodeJSON(resp, &result)
}

// CreateSchedule creates a new on-call schedule.
func (c *Client) CreateSchedule(s Schedule) (*Schedule, error) {
	data := make(map[string]Schedule)
	data["schedule"] = s
	resp, err := c.post("/schedules", data, nil)
	if err != nil {
		return nil, err
	}
	return getScheduleFromResponse(c, resp)
}

// PreviewScheduleOptions is the data structure used when calling the PreviewSchedule API endpoint.
type PreviewScheduleOptions struct {
	APIListObject
	Since    string `url:"since,omitempty"`
	Until    string `url:"until,omitempty"`
	Overflow bool   `url:"overflow,omitempty"`
}

// PreviewSchedule previews what an on-call schedule would look like without saving it.
func (c *Client) PreviewSchedule(s Schedule, o PreviewScheduleOptions) error {
	v, err := query.Values(o)
	if err != nil {
		return err
	}
	var data map[string]Schedule
	data["schedule"] = s
	_, e := c.post("/schedules/preview?"+v.Encode(), data, nil)
	return e
}

// DeleteSchedule deletes an on-call schedule.
func (c *Client) DeleteSchedule(id string) error {
	_, err := c.delete("/schedules/" + id)
	return err
}

// GetScheduleOptions is the data structure used when calling the GetSchedule API endpoint.
type GetScheduleOptions struct {
	APIListObject
	TimeZone string `url:"time_zone,omitempty"`
	Since    string `url:"since,omitempty"`
	Until    string `url:"until,omitempty"`
}

// GetSchedule shows detailed information about a schedule, including entries for each layer and sub-schedule.
func (c *Client) GetSchedule(id string, o GetScheduleOptions) (*Schedule, error) {
	v, err := query.Values(o)
	if err != nil {
		return nil, fmt.Errorf("Could not parse values for query: %v", err)
	}
	resp, err := c.get("/schedules/" + id + "?" + v.Encode())
	if err != nil {
		return nil, err
	}
	return getScheduleFromResponse(c, resp)
}

// UpdateScheduleOptions is the data structure used when calling the UpdateSchedule API endpoint.
type UpdateScheduleOptions struct {
	Overflow bool `url:"overflow,omitempty"`
}

// UpdateSchedule updates an existing on-call schedule.
func (c *Client) UpdateSchedule(id string, s Schedule) (*Schedule, error) {
	v := make(map[string]Schedule)
	v["schedule"] = s
	resp, err := c.put("/schedules/"+id, v, nil)
	if err != nil {
		return nil, err
	}
	return getScheduleFromResponse(c, resp)
}

// ListOverridesOptions is the data structure used when calling the ListOverrides API endpoint.
type ListOverridesOptions struct {
	APIListObject
	Since    string `url:"since,omitempty"`
	Until    string `url:"until,omitempty"`
	Editable bool   `url:"editable,omitempty"`
	Overflow bool   `url:"overflow,omitempty"`
}

// ListOverridesResponse is the data structure returned from calling the ListOverrides API endpoint.
type ListOverridesResponse struct {
	APIListObject
	Overrides []Override `json:"overrides,omitempty"`
}

// Overrides are any schedule layers from the override layer.
type Override struct {
	ID    string    `json:"id,omitempty"`
	Start string    `json:"start,omitempty"`
	End   string    `json:"end,omitempty"`
	User  APIObject `json:"user,omitempty"`
}

// ListOverrides lists overrides for a given time range.
func (c *Client) ListOverrides(id string, o ListOverridesOptions) (*ListOverridesResponse, error) {
	v, err := query.Values(o)
	if err != nil {
		return nil, err
	}
	resp, err := c.get("/schedules/" + id + "/overrides?" + v.Encode())
	if err != nil {
		return nil, err
	}
	var result ListOverridesResponse
	return &result, c.decodeJSON(resp, &result)
}

// CreateOverride creates an override for a specific user covering the specified time range.
func (c *Client) CreateOverride(id string, o Override) (*Override, error) {
	data := make(map[string]Override)
	data["override"] = o
	resp, err := c.post("/schedules/"+id+"/overrides", data, nil)
	if err != nil {
		return nil, err
	}
	return getOverrideFromResponse(c, resp)
}

// DeleteOverride removes an override.
func (c *Client) DeleteOverride(scheduleID, overrideID string) error {
	_, err := c.delete("/schedules/" + scheduleID + "/overrides/" + overrideID)
	return err
}

// ListOnCallUsersOptions is the data structure used when calling the ListOnCallUsers API endpoint.
type ListOnCallUsersOptions struct {
	APIListObject
	Since string `url:"since,omitempty"`
	Until string `url:"until,omitempty"`
}

// ListOnCallUsers lists all of the users on call in a given schedule for a given time range.
func (c *Client) ListOnCallUsers(id string, o ListOnCallUsersOptions) ([]User, error) {
	v, err := query.Values(o)
	if err != nil {
		return nil, err
	}
	resp, err := c.get("/schedules/" + id + "/users?" + v.Encode())
	if err != nil {
		return nil, err
	}
	var result map[string][]User
	if err := c.decodeJSON(resp, &result); err != nil {
		return nil, err
	}
	u, ok := result["users"]
	if !ok {
		return nil, fmt.Errorf("JSON response does not have users field")
	}
	return u, nil
}

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

func getOverrideFromResponse(c *Client, resp *http.Response) (*Override, error) {
	var target map[string]Override
	if dErr := c.decodeJSON(resp, &target); dErr != nil {
		return nil, fmt.Errorf("Could not decode JSON response: %v", dErr)
	}
	rootNode := "override"
	o, nodeOK := target[rootNode]
	if !nodeOK {
		return nil, fmt.Errorf("JSON response does not have %s field", rootNode)
	}
	return &o, nil
}
