package svc

import (
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"

	"github.com/CiscoM31/godata"
	libregraph "github.com/owncloud/libre-graph-api-go"
	"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
	"github.com/owncloud/ocis/v2/services/graph/pkg/identity"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/render"
)

// GetEducationSchools implements the Service interface.
func (g Graph) GetEducationSchools(w http.ResponseWriter, r *http.Request) {
	logger := g.logger.SubloggerWithRequestID(r.Context())
	logger.Info().Interface("query", r.URL.Query()).Msg("calling get schools")
	sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
	odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query())
	if err != nil {
		logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get schools: query error")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
		return
	}

	schools, err := g.identityEducationBackend.GetEducationSchools(r.Context())
	if err != nil {
		logger.Debug().Err(err).Msg("could not get schools: backend error")
		errorcode.RenderError(w, r, err)
		return
	}

	schools, err = sortEducationSchools(odataReq, schools)
	if err != nil {
		logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("cannot get schools: could not sort schools according to query")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
		return
	}
	render.Status(r, http.StatusOK)
	render.JSON(w, r, &ListResponse{Value: schools})
}

// PostEducationSchool implements the Service interface.
func (g Graph) PostEducationSchool(w http.ResponseWriter, r *http.Request) {
	logger := g.logger.SubloggerWithRequestID(r.Context())
	logger.Info().Msg("calling post school")
	school := libregraph.NewEducationSchool()
	err := StrictJSONUnmarshal(r.Body, school)
	if err != nil {
		logger.Debug().Err(err).Interface("body", r.Body).Msg("could not create school: invalid request body")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid request body: %s", err.Error()))
		return
	}

	// Disallow user-supplied IDs. It's supposed to be readonly. We're either
	// generating them in the backend ourselves or rely on the Backend's
	// storage (e.g. LDAP) to provide a unique ID.
	if _, ok := school.GetIdOk(); ok {
		logger.Debug().Msg("could not create school: id is a read-only attribute")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "school id is a read-only attribute")
		return
	}

	if _, ok := school.GetDisplayNameOk(); !ok {
		logger.Debug().Interface("school", school).Msg("could not create school: missing required attribute")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Missing Required Attribute")
		return
	}

	if _, ok := school.GetSchoolNumberOk(); !ok {
		logger.Debug().Interface("school", school).Msg("could not create school: missing required attribute")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Missing Required Attribute")
		return
	}

	// validate terminationDate attribute, needs to be "far enough" in the future, terminationDate can be nil (means
	// termination date is to be deleted
	if terminationDate, ok := school.GetTerminationDateOk(); ok && terminationDate != nil {
		err = g.validateTerminationDate(*terminationDate)
		if err != nil {
			errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
		}
		return
	}

	if school, err = g.identityEducationBackend.CreateEducationSchool(r.Context(), *school); err != nil {
		logger.Debug().Err(err).Interface("school", school).Msg("could not create school: backend error")
		errorcode.RenderError(w, r, err)
		return
	}

	/* TODO requires reva changes
	if school != nil && school.Id != nil {
		e := events.SchoolCreated{SchoolID: *school.Id}
		if currentUser, ok := ctxpkg.ContextGetUser(r.Context()); ok {
			e.Executant = currentUser.GetId()
		}
		g.publishEvent(e)
	}
	*/

	render.Status(r, http.StatusCreated)
	render.JSON(w, r, school)
}

// PatchEducationSchool implements the Service interface.
func (g Graph) PatchEducationSchool(w http.ResponseWriter, r *http.Request) {
	logger := g.logger.SubloggerWithRequestID(r.Context())
	schoolID := chi.URLParam(r, "schoolID")
	logger.Info().Str("schoolID", schoolID).Msg("calling patch school")
	schoolID, err := url.PathUnescape(schoolID)
	if err != nil {
		logger.Debug().Str("id", schoolID).Msg("could not update school: unescaping school id failed")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping school id failed")
		return
	}

	if schoolID == "" {
		logger.Debug().Msg("could not update school: missing school id")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing school id")
		return
	}

	school := libregraph.NewEducationSchool()
	err = StrictJSONUnmarshal(r.Body, school)
	if err != nil {
		logger.Debug().Err(err).Interface("body", r.Body).Msg("could not update school: invalid request body")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid request body: %s", err.Error()))
		return
	}

	// validate terminationDate attribute, needs to be "far enough" in the future, terminationDate can be nil (means
	// termination date is to be deleted
	if terminationDate, ok := school.GetTerminationDateOk(); ok && terminationDate != nil {
		err = g.validateTerminationDate(*terminationDate)
		if err != nil {
			errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
			return
		}
	}

	if school, err = g.identityEducationBackend.UpdateEducationSchool(r.Context(), schoolID, *school); err != nil {
		logger.Debug().Err(err).Interface("school", school).Msg("could not update school: backend error")
		errorcode.RenderError(w, r, err)
		return
	}

	/* TODO requires reva changes
	e := events.SchoolFeatureChanged{SchoolID: schoolID}
	if currentUser, ok := ctxpkg.ContextGetUser(r.Context()); ok {
		e.Executant = currentUser.GetId()
	}
	g.publishEvent(e)
	*/

	render.Status(r, http.StatusOK)
	render.JSON(w, r, school)
}

// GetEducationSchool implements the Service interface.
func (g Graph) GetEducationSchool(w http.ResponseWriter, r *http.Request) {
	logger := g.logger.SubloggerWithRequestID(r.Context())
	schoolID := chi.URLParam(r, "schoolID")
	logger.Info().Str("schoolID", schoolID).Msg("calling get school")
	schoolID, err := url.PathUnescape(schoolID)
	if err != nil {
		logger.Debug().Str("id", schoolID).Msg("could not get school: unescaping school id failed")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping school id failed")
	}

	if schoolID == "" {
		logger.Debug().Msg("could not get school: missing school id")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing school id")
		return
	}

	logger.Debug().
		Str("id", schoolID).
		Interface("query", r.URL.Query()).
		Msg("calling get school on backend")
	school, err := g.identityEducationBackend.GetEducationSchool(r.Context(), schoolID)
	if err != nil {
		logger.Debug().Err(err).Msg("could not get school: backend error")
		errorcode.RenderError(w, r, err)
		return
	}

	render.Status(r, http.StatusOK)
	render.JSON(w, r, school)
}

// DeleteEducationSchool implements the Service interface.
func (g Graph) DeleteEducationSchool(w http.ResponseWriter, r *http.Request) {
	logger := g.logger.SubloggerWithRequestID(r.Context())
	schoolID := chi.URLParam(r, "schoolID")
	logger.Info().Str("schoolID", schoolID).Msg("calling delete school")
	schoolID, err := url.PathUnescape(schoolID)
	if err != nil {
		logger.Debug().Err(err).Str("id", schoolID).Msg("could not delete school: unescaping school id failed")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping school id failed")
		return
	}

	if schoolID == "" {
		logger.Debug().Msg("could not delete school: missing school id")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing school id")
		return
	}

	// Read school and check if termination date is set
	school, err := g.identityEducationBackend.GetEducationSchool(r.Context(), schoolID)
	if err != nil {
		logger.Debug().Err(err).Msg("could not get school: backend error")
		errorcode.RenderError(w, r, err)
		return
	}
	termination, ok := school.GetTerminationDateOk()
	if !ok {
		logger.Debug().Msg("cannot delete school: not termination date set")
		errorcode.NotAllowed.Render(w, r, http.StatusMethodNotAllowed, "no termination date set")
		return
	}

	if time.Now().Before(*termination) {
		logger.Debug().Time("terminationDate", *termination).Msg("cannot delete school: termination date not reached")
		errorcode.NotAllowed.Render(w, r, http.StatusMethodNotAllowed, "can't delete school before termination date")
		return
	}

	logger.Debug().Str("schoolID", schoolID).Msg("Getting users of school")
	users, err := g.identityEducationBackend.GetEducationSchoolUsers(r.Context(), schoolID)
	if err != nil {
		logger.Debug().Err(err).Msg("could not get school users: backend error")
		errorcode.RenderError(w, r, err)
		return
	}

	for _, user := range users {
		logger.Debug().Str("schoolID", schoolID).Str("userID", *user.Id).Msg("calling delete member on backend")
		if err := g.identityEducationBackend.RemoveUserFromEducationSchool(r.Context(), schoolID, *user.Id); err != nil {
			if errors.Is(err, identity.ErrNotFound) {
				logger.Debug().Str("schoolID", schoolID).Str("userID", *user.Id).Msg("user not found")
				continue
			}
			logger.Debug().Err(err).Msg("could not delete school member: backend error")
			errorcode.RenderError(w, r, err)
			// TODO Do we need return right hear?
		}
	}

	logger.Debug().Str("id", schoolID).Msg("calling delete school on backend")
	err = g.identityEducationBackend.DeleteEducationSchool(r.Context(), schoolID)

	if err != nil {
		logger.Debug().Err(err).Msg("could not delete school: backend error")
		errorcode.RenderError(w, r, err)
		return
	}

	/* TODO requires reva changes
	e := events.SchoolDeleted{SchoolID: schoolID}
	if currentUser, ok := ctxpkg.ContextGetUser(r.Context()); ok {
		e.Executant = currentUser.GetId()
	}
	g.publishEvent(e)
	*/

	render.Status(r, http.StatusNoContent)
	render.NoContent(w, r)
}

// GetEducationSchoolUsers implements the Service interface.
func (g Graph) GetEducationSchoolUsers(w http.ResponseWriter, r *http.Request) {
	logger := g.logger.SubloggerWithRequestID(r.Context())
	schoolID := chi.URLParam(r, "schoolID")
	logger.Info().Str("schoolID", schoolID).Msg("calling get school users")
	schoolID, err := url.PathUnescape(schoolID)
	if err != nil {
		logger.Debug().Str("id", schoolID).Msg("could not get school users: unescaping school id failed")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping school id failed")
		return
	}

	if schoolID == "" {
		logger.Debug().Msg("could not get school users: missing school id")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing school id")
		return
	}

	logger.Debug().Str("id", schoolID).Msg("calling get school users on backend")
	users, err := g.identityEducationBackend.GetEducationSchoolUsers(r.Context(), schoolID)
	if err != nil {
		logger.Debug().Err(err).Msg("could not get school users: backend error")
		errorcode.RenderError(w, r, err)
		return
	}

	render.Status(r, http.StatusOK)
	render.JSON(w, r, users)
}

// PostEducationSchoolUser implements the Service interface.
func (g Graph) PostEducationSchoolUser(w http.ResponseWriter, r *http.Request) {
	logger := g.logger.SubloggerWithRequestID(r.Context())

	schoolID := chi.URLParam(r, "schoolID")
	logger.Info().Str("schoolID", schoolID).Msg("Calling post school user")
	schoolID, err := url.PathUnescape(schoolID)
	if err != nil {
		logger.Debug().
			Err(err).
			Str("id", schoolID).
			Msg("could not add user to school: unescaping school id failed")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping school id failed")
		return
	}

	if schoolID == "" {
		logger.Debug().Msg("could not add school user: missing school id")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing school id")
		return
	}
	memberRef := libregraph.NewMemberReference()
	err = StrictJSONUnmarshal(r.Body, memberRef)
	if err != nil {
		logger.Debug().
			Err(err).
			Interface("body", r.Body).
			Msg("could not add school user: invalid request body")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid request body: %s", err.Error()))
		return
	}
	memberRefURL, ok := memberRef.GetOdataIdOk()
	if !ok {
		logger.Debug().Msg("could not add school user: @odata.id reference is missing")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "@odata.id reference is missing")
		return
	}
	memberType, id, err := g.parseMemberRef(*memberRefURL)
	if err != nil {
		logger.Debug().Err(err).Msg("could not add school user: error parsing @odata.id url")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Error parsing @odata.id url")
		return
	}
	// The MS Graph spec allows "directoryObject", "user", "school" and "organizational Contact"
	// we restrict this to users for now. Might add Schools as members later
	if memberType != "users" {
		logger.Debug().Str("type", memberType).Msg("could not add school user: Only users are allowed as school members")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Only users are allowed as school members")
		return
	}

	logger.Debug().Str("memberType", memberType).Str("id", id).Msg("calling add user on backend")
	err = g.identityEducationBackend.AddUsersToEducationSchool(r.Context(), schoolID, []string{id})

	if err != nil {
		logger.Debug().Err(err).Msg("could not add school user: backend error")
		errorcode.RenderError(w, r, err)
		return
	}

	/* TODO requires reva changes
	e := events.SchoolMemberAdded{SchoolID: schoolID, UserID: id}
	if currentUser, ok := ctxpkg.ContextGetUser(r.Context()); ok {
		e.Executant = currentUser.GetId()
	}
	g.publishEvent(e)
	*/

	render.Status(r, http.StatusNoContent)
	render.NoContent(w, r)
}

// DeleteEducationSchoolUser implements the Service interface.
func (g Graph) DeleteEducationSchoolUser(w http.ResponseWriter, r *http.Request) {
	logger := g.logger.SubloggerWithRequestID(r.Context())

	schoolID := chi.URLParam(r, "schoolID")
	userID := chi.URLParam(r, "userID")
	logger.Info().Str("schoolID", schoolID).Str("userID", userID).Msg("calling delete school member")
	schoolID, err := url.PathUnescape(schoolID)
	if err != nil {
		logger.Debug().Err(err).Str("id", schoolID).Msg("could not delete school member: unescaping school id failed")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping school id failed")
		return
	}

	if schoolID == "" {
		logger.Debug().Msg("could not delete school member: missing school id")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing school id")
		return
	}

	userID, err = url.PathUnescape(userID)
	if err != nil {
		logger.Debug().Err(err).Str("id", userID).Msg("could not delete school member: unescaping member id failed")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping member id failed")
		return
	}

	if userID == "" {
		logger.Debug().Msg("could not delete school member: missing member id")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing member id")
		return
	}
	logger.Debug().Str("schoolID", schoolID).Str("userID", userID).Msg("calling delete member on backend")
	err = g.identityEducationBackend.RemoveUserFromEducationSchool(r.Context(), schoolID, userID)

	if err != nil {
		logger.Debug().Err(err).Msg("could not delete school member: backend error")
		errorcode.RenderError(w, r, err)
		return
	}

	/* TODO requires reva changes
	e := events.SchoolMemberRemoved{SchoolID: schoolID, UserID: userID}
	if currentUser, ok := ctxpkg.ContextGetUser(r.Context()); ok {
		e.Executant = currentUser.GetId()
	}
	g.publishEvent(e)
	*/

	render.Status(r, http.StatusNoContent)
	render.NoContent(w, r)
}

// GetEducationSchoolClasses implements the Service interface.
func (g Graph) GetEducationSchoolClasses(w http.ResponseWriter, r *http.Request) {
	logger := g.logger.SubloggerWithRequestID(r.Context())
	schoolID := chi.URLParam(r, "schoolID")
	logger.Info().Str("schoolID", schoolID).Msg("calling get school classes")
	schoolID, err := url.PathUnescape(schoolID)
	if err != nil {
		logger.Debug().Str("id", schoolID).Msg("could not get school users: unescaping school id failed")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping school id failed")
		return
	}

	if schoolID == "" {
		logger.Debug().Msg("could not get school users: missing school id")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing school id")
		return
	}

	logger.Debug().Str("id", schoolID).Msg("calling get school classes on backend")
	classes, err := g.identityEducationBackend.GetEducationSchoolClasses(r.Context(), schoolID)
	if err != nil {
		logger.Debug().Err(err).Msg("could not get school classes: backend error")
		errorcode.RenderError(w, r, err)
		return
	}

	render.Status(r, http.StatusOK)
	render.JSON(w, r, classes)
}

// PostEducationSchoolClass implements the Service interface.
func (g Graph) PostEducationSchoolClass(w http.ResponseWriter, r *http.Request) {
	logger := g.logger.SubloggerWithRequestID(r.Context())

	schoolID := chi.URLParam(r, "schoolID")
	logger.Info().Str("schoolID", schoolID).Msg("Calling post school class")
	schoolID, err := url.PathUnescape(schoolID)
	if err != nil {
		logger.Debug().
			Err(err).
			Str("id", schoolID).
			Msg("could not add class to school: unescaping school id failed")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping school id failed")
		return
	}

	if schoolID == "" {
		logger.Debug().Msg("could not add school class: missing school id")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing school id")
		return
	}
	memberRef := libregraph.NewMemberReference()
	err = StrictJSONUnmarshal(r.Body, memberRef)
	if err != nil {
		logger.Debug().
			Err(err).
			Interface("body", r.Body).
			Msg("could not add school class: invalid request body")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid request body: %s", err.Error()))
		return
	}
	memberRefURL, ok := memberRef.GetOdataIdOk()
	if !ok {
		logger.Debug().Msg("could not add school class: @odata.id reference is missing")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "@odata.id reference is missing")
		return
	}
	memberType, id, err := g.parseMemberRef(*memberRefURL)
	if err != nil {
		logger.Debug().Err(err).Msg("could not add school class: error parsing @odata.id url")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Error parsing @odata.id url")
		return
	}
	// The MS Graph spec allows "directoryObject", "user", "school" and "organizational Contact"
	// we restrict this to users for now. Might add Schools as members later
	if memberType != "classes" {
		logger.Debug().Str("type", memberType).Msg("could not add school class: Only classes are allowed as school members")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Only classes are allowed as school members")
		return
	}

	logger.Debug().Str("memberType", memberType).Str("id", id).Msg("calling add class on backend")
	err = g.identityEducationBackend.AddClassesToEducationSchool(r.Context(), schoolID, []string{id})

	if err != nil {
		logger.Debug().Err(err).Msg("could not add school class: backend error")
		errorcode.RenderError(w, r, err)
		return
	}

	/* TODO requires reva changes
	e := events.SchoolMemberAdded{SchoolID: schoolID, UserID: id}
	if currentUser, ok := ctxpkg.ContextGetUser(r.Context()); ok {
		e.Executant = currentUser.GetId()
	}
	g.publishEvent(e)
	*/

	render.Status(r, http.StatusNoContent)
	render.NoContent(w, r)
}

// DeleteEducationSchoolClass implements the Service interface.
func (g Graph) DeleteEducationSchoolClass(w http.ResponseWriter, r *http.Request) {
	logger := g.logger.SubloggerWithRequestID(r.Context())

	schoolID := chi.URLParam(r, "schoolID")
	classID := chi.URLParam(r, "classID")
	logger.Info().Str("schoolID", schoolID).Str("classID", classID).Msg("calling delete school class")
	schoolID, err := url.PathUnescape(schoolID)
	if err != nil {
		logger.Debug().Err(err).Str("id", schoolID).Msg("could not delete school class: unescaping school id failed")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping school id failed")
		return
	}

	if schoolID == "" {
		logger.Debug().Msg("could not delete school class: missing school id")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing school id")
		return
	}

	classID, err = url.PathUnescape(classID)
	if err != nil {
		logger.Debug().Err(err).Str("id", classID).Msg("could not delete school class: unescaping class id failed")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping class id failed")
		return
	}

	if classID == "" {
		logger.Debug().Msg("could not delete school class: missing class id")
		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing class id")
		return
	}
	logger.Debug().Str("schoolID", schoolID).Str("classID", classID).Msg("calling delete class on backend")
	err = g.identityEducationBackend.RemoveClassFromEducationSchool(r.Context(), schoolID, classID)

	if err != nil {
		logger.Debug().Err(err).Msg("could not delete school class: backend error")
		errorcode.RenderError(w, r, err)
		return
	}

	/* TODO requires reva changes
	e := events.SchoolMemberRemoved{SchoolID: schoolID, UserID: userID}
	if currentUser, ok := ctxpkg.ContextGetUser(r.Context()); ok {
		e.Executant = currentUser.GetId()
	}
	g.publishEvent(e)
	*/

	render.Status(r, http.StatusNoContent)
	render.NoContent(w, r)
}

func (g Graph) validateTerminationDate(terminationDate time.Time) error {
	if terminationDate.Before(time.Now()) {
		return fmt.Errorf("can not set a termination date in the past")
	}
	graceDays := g.config.Identity.LDAP.EducationConfig.SchoolTerminationGraceDays
	if graceDays != 0 {
		if terminationDate.Before(time.Now().Add(time.Duration(graceDays) * 24 * time.Hour)) {
			return fmt.Errorf("termination needs to be at least %d day(s) in the future", graceDays)
		}
	}
	return nil
}

func sortEducationSchools(req *godata.GoDataRequest, schools []*libregraph.EducationSchool) ([]*libregraph.EducationSchool, error) {
	if req.Query.OrderBy == nil || len(req.Query.OrderBy.OrderByItems) != 1 {
		return schools, nil
	}
	var less func(i, j int) bool

	switch req.Query.OrderBy.OrderByItems[0].Field.Value {
	case displayNameAttr:
		less = func(i, j int) bool {
			return strings.ToLower(schools[i].GetDisplayName()) < strings.ToLower(schools[j].GetDisplayName())
		}
	default:
		return nil, fmt.Errorf("we do not support <%s> as a order parameter", req.Query.OrderBy.OrderByItems[0].Field.Value)
	}

	if req.Query.OrderBy.OrderByItems[0].Order == _sortDescending {
		sort.Slice(schools, reverse(less))
	} else {
		sort.Slice(schools, less)
	}

	return schools, nil
}
