package cfclient

import (
	"encoding/json"
	"io/ioutil"
	"time"

	"github.com/pkg/errors"
)

// Exported event constants
const (
	AppCrash      = "app.crash"
	AppStart      = "audit.app.start"
	AppStop       = "audit.app.stop"
	AppUpdate     = "audit.app.update"
	AppCreate     = "audit.app.create"
	AppDelete     = "audit.app.delete-request"
	AppSSHAuth    = "audit.app.ssh-authorized"
	AppSSHUnauth  = "audit.app.ssh-unauthorized"
	AppRestage    = "audit.app.restage"
	AppMapRoute   = "audit.app.map-route"
	AppUnmapRoute = "audit.app.unmap-route"
)

// Exported filter constants
const (
	FilterTimestamp = "timestamp"
	FilterActee     = "actee"
)

// ValidOperators global variable for all valid operators in a query
var ValidOperators = []string{":", ">=", "<=", "<", ">", "IN"}

// AppEventResponse the entire response
type AppEventResponse struct {
	Results   int                `json:"total_results"`
	Pages     int                `json:"total_pages"`
	PrevURL   string             `json:"prev_url"`
	NextURL   string             `json:"next_url"`
	Resources []AppEventResource `json:"resources"`
}

// AppEventResource the event resources
type AppEventResource struct {
	Meta   Meta           `json:"metadata"`
	Entity AppEventEntity `json:"entity"`
}

// AppEventQuery a struct for defining queries like 'q=filter>value' or 'q=filter IN a,b,c'
type AppEventQuery struct {
	Filter   string
	Operator string
	Value    string
}

// The AppEventEntity the actual app event body
type AppEventEntity struct {
	// EventTypes are app.crash, audit.app.start, audit.app.stop, audit.app.update, audit.app.create, audit.app.delete-request
	EventType string `json:"type"`
	// The GUID of the actor.
	Actor string `json:"actor"`
	// The actor type, user or app
	ActorType string `json:"actor_type"`
	// The name of the actor.
	ActorName string `json:"actor_name"`
	// The GUID of the actee.
	Actee string `json:"actee"`
	// The actee type, space, app or v3-app
	ActeeType string `json:"actee_type"`
	// The name of the actee.
	ActeeName string `json:"actee_name"`
	// Timestamp format "2016-02-26T13:29:44Z". The event creation time.
	Timestamp time.Time `json:"timestamp"`
	MetaData  struct {
		// app.crash event fields
		ExitDescription string `json:"exit_description,omitempty"`
		ExitReason      string `json:"reason,omitempty"`
		ExitStatus      string `json:"exit_status,omitempty"`

		Request struct {
			Name              string  `json:"name,omitempty"`
			Instances         float64 `json:"instances,omitempty"`
			State             string  `json:"state,omitempty"`
			Memory            float64 `json:"memory,omitempty"`
			EnvironmentVars   string  `json:"environment_json,omitempty"`
			DockerCredentials string  `json:"docker_credentials_json,omitempty"`
			// audit.app.create event fields
			Console            bool    `json:"console,omitempty"`
			Buildpack          string  `json:"buildpack,omitempty"`
			Space              string  `json:"space_guid,omitempty"`
			HealthcheckType    string  `json:"health_check_type,omitempty"`
			HealthcheckTimeout float64 `json:"health_check_timeout,omitempty"`
			Production         bool    `json:"production,omitempty"`
			// app.crash event fields
			Index float64 `json:"index,omitempty"`
		} `json:"request"`
	} `json:"metadata"`
}

// ListAppEvents returns all app events based on eventType
func (c *Client) ListAppEvents(eventType string) ([]AppEventEntity, error) {
	return c.ListAppEventsByQuery(eventType, nil)
}

// ListAppEventsByQuery returns all app events based on eventType and queries
func (c *Client) ListAppEventsByQuery(eventType string, queries []AppEventQuery) ([]AppEventEntity, error) {
	var events []AppEventEntity

	if eventType != AppCrash && eventType != AppStart && eventType != AppStop && eventType != AppUpdate && eventType != AppCreate &&
		eventType != AppDelete && eventType != AppSSHAuth && eventType != AppSSHUnauth && eventType != AppRestage &&
		eventType != AppMapRoute && eventType != AppUnmapRoute {
		return nil, errors.New("Unsupported app event type " + eventType)
	}

	var query = "/v2/events?q=type:" + eventType
	// adding the additional queries
	if len(queries) > 0 {
		for _, eventQuery := range queries {
			if eventQuery.Filter != FilterTimestamp && eventQuery.Filter != FilterActee {
				return nil, errors.New("Unsupported query filter type " + eventQuery.Filter)
			}
			if !stringInSlice(eventQuery.Operator, ValidOperators) {
				return nil, errors.New("Unsupported query operator type " + eventQuery.Operator)
			}
			query += "&q=" + eventQuery.Filter + eventQuery.Operator + eventQuery.Value
		}
	}

	for {
		eventResponse, err := c.getAppEventsResponse(query)
		if err != nil {
			return []AppEventEntity{}, err
		}
		for _, event := range eventResponse.Resources {
			events = append(events, event.Entity)
		}
		query = eventResponse.NextURL
		if query == "" {
			break
		}
	}

	return events, nil
}

func (c *Client) getAppEventsResponse(query string) (AppEventResponse, error) {
	var eventResponse AppEventResponse
	r := c.NewRequest("GET", query)
	resp, err := c.DoRequest(r)
	if err != nil {
		return AppEventResponse{}, errors.Wrap(err, "Error requesting appevents")
	}
	defer resp.Body.Close()
	resBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return AppEventResponse{}, errors.Wrap(err, "Error reading appevents response body")
	}

	err = json.Unmarshal(resBody, &eventResponse)
	if err != nil {
		return AppEventResponse{}, errors.Wrap(err, "Error unmarshalling appevent")
	}
	return eventResponse, nil
}

func stringInSlice(str string, list []string) bool {
	for _, v := range list {
		if v == str {
			return true
		}
	}
	return false
}
