//ValidateEventTrackingPayloadHandler validates that the payload has a valid JSON Schema
//Uses a FinalHandler as next because it consumes the request's body
func ValidateEventTrackingPayloadHandler(next *ctx.FinalHandler) *ctx.Handler {
	return ctx.NewHandler(next.Context, func(c *ctx.Context, w http.ResponseWriter, r *http.Request) {
		//Validate the payload
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			c.Logger.Errorf("%s : Error reading body: %s", utilities.GetIP(r), err.Error())
			http.Error(w, http.StatusText(500), 500)
			return
		}
		next.Payload = body
		requestLoader := gojsonschema.NewStringLoader(string(body))

		result, err := c.JSONTrackingEventValidator.Schema.Validate(requestLoader)
		if err != nil {
			c.Logger.Infof("%s : Json validation error: %s", utilities.GetIP(r), err.Error())
			http.Error(w, http.StatusText(400), 400)
			return
		}

		if !result.Valid() {
			c.Logger.Infof("%s : Payload is not valid: %+v", utilities.GetIP(r), result.Errors())
			http.Error(w, http.StatusText(400), 400)
			return
		}
		next.ServeHTTP(w, r)
	})
}
//AuthHandler middleware
//This handler handles auth based on the assertion that the request is valid JSON
//Verifies for access, blocks handlers chain if access denied
func AuthHandler(next *ctx.Handler) *ctx.Handler {
	return ctx.NewHandler(next.Context, func(c *ctx.Context, w http.ResponseWriter, r *http.Request) {
		//TODO: This can change for body instead ?
		token := r.Header.Get("Tracking-Token")
		//Bad request token empty or not present
		if token == "" {
			c.Logger.Infof("%s : No token header", utilities.GetIP(r))
			token = r.URL.Query().Get("key")
			if token == "" {
				c.Logger.Infof("%s : No token query parameter", utilities.GetIP(r))
				http.Error(w, http.StatusText(400), 400)
				return
			}
		}

		authorized, err := c.AuthDb.IsTokenAuthorized(token)

		// Internal server error TODO: Handle this
		if err != nil {
			c.Logger.Errorf("%s : Error while authorizing: %s", utilities.GetIP(r), err.Error())
			http.Error(w, http.StatusText(500), 500)
			return
		}
		// Unauthorized
		if !authorized {
			c.Logger.Warnf("%s : Unauthorized: %s", utilities.GetIP(r), token)
			http.Error(w, http.StatusText(401), 401)
			return
		}
		c.Logger.Infof("%s : Authorized", utilities.GetIP(r))
		next.ServeHTTP(w, r)
	})
}
//EnforceJSONHandler middleware
//This handler can handle raw requests
//This handler checks for detected content type as well as content-type header
func EnforceJSONHandler(next *ctx.Handler) *ctx.Handler {
	return ctx.NewHandler(next.Context, func(c *ctx.Context, w http.ResponseWriter, r *http.Request) {
		//Ensure that there is a body
		if r.ContentLength == 0 {
			c.Logger.Infof("%s : Recieved empty payload", utilities.GetIP(r))
			http.Error(w, http.StatusText(400), 400)
			return
		}
		//Ensure that its json
		contentType := r.Header.Get("Content-Type")
		if contentType != "application/json; charset=UTF-8" {
			c.Logger.Infof("%s : Invalid content type. Got %s", utilities.GetIP(r), contentType)
			http.Error(w, http.StatusText(415), 415)
			return
		}
		next.ServeHTTP(w, r)
	})
}
//Handler for /track route
//From here we have a valid authentified json request with correct schema
func handleTrack(c *ctx.Context, p []byte, w http.ResponseWriter, r *http.Request) {
	err := c.Queue.PublishEventsTrackingTask(p)
	if err != nil {
		//TODO : Could we handle this a little better?
		c.Logger.Errorf("Error publishing to queue: %s\nPayload: %s", err.Error(), string(p))
		http.Error(w, http.StatusText(500), 500)
	}
	c.Logger.Infof("%s : Valid payload recieved and treated", utilities.GetIP(r))
}
//Handler for /connected route
//Allows websocket connection
//From here we have an authentified request
func handleConnected(c *ctx.Context, w http.ResponseWriter, r *http.Request) {
	const pongWait time.Duration = 10 * time.Second
	const writeWait time.Duration = 10 * time.Second

	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		//TODO : Could we handle this a little better?
		c.Logger.Errorf("%s : Could not upgrade to websocket protocol: %s", utilities.GetIP(r), err.Error())
		http.Error(w, http.StatusText(500), 500)
		return
	}
	//We defer the closing of the connection
	defer conn.Close()
	//Custom settings for timeouts
	conn.SetReadDeadline(time.Now().Add(pongWait))
	conn.SetPongHandler(func(string) error {
		conn.SetReadDeadline(time.Now().Add(pongWait))
		return nil
	})

	for {
		messageType, payload, err := conn.ReadMessage()
		if err != nil {
			if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
				c.Logger.Infof("%s : Connection timeout: %s", utilities.GetIP(r), err.Error())
				err = conn.WriteControl(websocket.CloseMessage, []byte("Timeout"), time.Now().Add(writeWait))
				return
			}
			c.Logger.Infof("%s : Error reading message: %s", utilities.GetIP(r), err.Error())
			err = conn.WriteControl(websocket.CloseInvalidFramePayloadData, []byte("Cannot read message"), time.Now().Add(writeWait))
			return
		}

		if messageType != websocket.TextMessage {
			c.Logger.Infof("%s : Unhandled message type : %d", utilities.GetIP(r), messageType)
			err = conn.WriteControl(websocket.CloseUnsupportedData, []byte("Unhandled message type"), time.Now().Add(writeWait))
			return
		}
		//We need to validate the payload and then send it
		payloadLoader := gojsonschema.NewStringLoader(string(payload))
		result, err := c.JSONTrackingEventValidator.Schema.Validate(payloadLoader)
		if err != nil {
			c.Logger.Infof("%s : Json validation error: %s", utilities.GetIP(r), err.Error())
			err = conn.WriteControl(websocket.CloseUnsupportedData, []byte("Json validation error"), time.Now().Add(writeWait))
			return
		}

		if !result.Valid() {
			c.Logger.Infof("%s : Payload is not valid: %+v", utilities.GetIP(r), result.Errors())
			conn.SetWriteDeadline(time.Now().Add(writeWait))
			err = conn.WriteJSON(&wsResponse{"ERROR", "Invalid payload"})
			return
		}
		err = c.Queue.PublishEventsTrackingTask(payload)
		if err != nil {
			//TODO : Could we handle this a little better?
			c.Logger.Errorf("%s : Error publishing to queue: %s\nPayload: %s", utilities.GetIP(r), err.Error(), string(payload))
			http.Error(w, http.StatusText(500), 500)
		}
		//TODO: ack the request
		c.Logger.Infof("%s : Valid payload recieved and treated", utilities.GetIP(r))
		conn.SetWriteDeadline(time.Now().Add(writeWait))
		conn.WriteJSON(&wsResponse{"OK", ""})
	}
}