package resource

import (
	"errors"
	"net/http"
	"sort"
	"strconv"

	"go-jsonapi-example/internal/model"
	"go-jsonapi-example/internal/storage"

	"github.com/manyminds/api2go"
)

// CarResource for api2go routes
type CarResource struct {
	CarStorage *storage.CarStorage
}

// FindAll to satisfy api2go data source interface
func (s CarResource) FindAll(r api2go.Request) (api2go.Responder, error) {
	var result []model.Car
	cars := s.CarStorage.GetAll()

	for _, car := range cars {
		result = append(result, *car)
	}

	return &Response{Res: result}, nil
}

// PaginatedFindAll can be used to load cars in chunks
func (s CarResource) PaginatedFindAll(r api2go.Request) (uint, api2go.Responder, error) {
	var (
		result                      []model.Car
		number, size, offset, limit string
		keys                        []int
	)
	cars := s.CarStorage.GetAll()

	for k := range cars {
		i, err := strconv.ParseInt(k, 10, 64)
		if err != nil {
			return 0, &Response{}, err
		}

		keys = append(keys, int(i))
	}
	sort.Ints(keys)

	numberQuery, ok := r.QueryParams["page[number]"]
	if ok {
		number = numberQuery[0]
	}
	sizeQuery, ok := r.QueryParams["page[size]"]
	if ok {
		size = sizeQuery[0]
	}
	offsetQuery, ok := r.QueryParams["page[offset]"]
	if ok {
		offset = offsetQuery[0]
	}
	limitQuery, ok := r.QueryParams["page[limit]"]
	if ok {
		limit = limitQuery[0]
	}

	if size != "" {
		sizeI, err := strconv.ParseUint(size, 10, 64)
		if err != nil {
			return 0, &Response{}, err
		}

		numberI, err := strconv.ParseUint(number, 10, 64)
		if err != nil {
			return 0, &Response{}, err
		}

		start := sizeI * (numberI - 1)
		for i := start; i < start+sizeI; i++ {
			if i >= uint64(len(cars)) {
				break
			}
			result = append(result, *cars[strconv.FormatInt(int64(keys[i]), 10)])
		}
	} else {
		limitI, err := strconv.ParseUint(limit, 10, 64)
		if err != nil {
			return 0, &Response{}, err
		}

		offsetI, err := strconv.ParseUint(offset, 10, 64)
		if err != nil {
			return 0, &Response{}, err
		}

		for i := offsetI; i < offsetI+limitI; i++ {
			if i >= uint64(len(cars)) {
				break
			}
			result = append(result, *cars[strconv.FormatInt(int64(keys[i]), 10)])
		}
	}

	return uint(len(cars)), &Response{Res: result}, nil
}

// FindOne to satisfy `api2go.DataSource` interface
// this method should return the car with the given ID, otherwise an error
func (s CarResource) FindOne(ID string, r api2go.Request) (api2go.Responder, error) {
	car, err := s.CarStorage.GetOne(ID)
	if err != nil {
		return &Response{}, api2go.NewHTTPError(err, err.Error(), http.StatusNotFound)
	}
	return &Response{Res: car}, nil
}

// Create method to satisfy `api2go.DataSource` interface
func (s CarResource) Create(obj interface{}, r api2go.Request) (api2go.Responder, error) {
	car, ok := obj.(model.Car)
	if !ok {
		return &Response{}, api2go.NewHTTPError(errors.New("Invalid instance given"), "Invalid instance given", http.StatusBadRequest)
	}

	if ok, httpErr := car.Verify(); !ok {
		return &Response{}, httpErr
	}

	id := s.CarStorage.Insert(car)
	car.ID = id

	return &Response{Res: car, Code: http.StatusCreated}, nil
}

// Delete to satisfy `api2go.DataSource` interface
func (s CarResource) Delete(id string, r api2go.Request) (api2go.Responder, error) {
	err := s.CarStorage.Delete(id)
	return &Response{Code: http.StatusNoContent}, err
}

//Update stores all changes on the car
func (s CarResource) Update(obj interface{}, r api2go.Request) (api2go.Responder, error) {
	car, ok := obj.(model.Car)
	if !ok {
		return &Response{}, api2go.NewHTTPError(errors.New("Invalid instance given"), "Invalid instance given", http.StatusBadRequest)
	}

	if ok, httpErr := car.Verify(); !ok {
		return &Response{}, httpErr
	}

	err := s.CarStorage.Update(car)
	return &Response{Code: http.StatusOK}, err
}
