package api

import (
	"encoding/json"
	"net/http"
	"net/url"

	"github.com/thermokarst/bactdb/errors"
	"github.com/thermokarst/bactdb/helpers"
	"github.com/thermokarst/bactdb/models"
	"github.com/thermokarst/bactdb/payloads"
	"github.com/thermokarst/bactdb/types"
)

// CharacteristicService provides for CRUD operations
type CharacteristicService struct{}

// Unmarshal satisfies interface Updater and interface Creater
func (c CharacteristicService) Unmarshal(b []byte) (types.Entity, error) {
	var cj payloads.Characteristic
	err := json.Unmarshal(b, &cj)
	return &cj, err
}

// List lists all characteristics
func (c CharacteristicService) List(val *url.Values, claims *types.Claims) (types.Entity, *types.AppError) {
	if val == nil {
		return nil, newJSONError(errors.ErrMustProvideOptions, http.StatusInternalServerError)
	}
	var opt helpers.ListOptions
	if err := helpers.SchemaDecoder.Decode(&opt, *val); err != nil {
		return nil, newJSONError(err, http.StatusInternalServerError)
	}

	characteristics, err := models.ListCharacteristics(opt, claims)
	if err != nil {
		return nil, newJSONError(err, http.StatusInternalServerError)
	}

	strainsOpt, err := models.StrainOptsFromCharacteristics(opt)
	if err != nil {
		return nil, newJSONError(err, http.StatusInternalServerError)
	}

	strains, err := models.ListStrains(*strainsOpt, claims)
	if err != nil {
		return nil, newJSONError(err, http.StatusInternalServerError)
	}

	speciesOpt, err := models.SpeciesOptsFromStrains(*strainsOpt)
	if err != nil {
		return nil, newJSONError(err, http.StatusInternalServerError)
	}

	species, err := models.ListSpecies(*speciesOpt, claims)
	if err != nil {
		return nil, newJSONError(err, http.StatusInternalServerError)
	}

	measurementsOpt, err := models.MeasurementOptsFromCharacteristics(opt)
	if err != nil {
		return nil, newJSONError(err, http.StatusInternalServerError)
	}

	measurements, err := models.ListMeasurements(*measurementsOpt, claims)
	if err != nil {
		return nil, newJSONError(err, http.StatusInternalServerError)
	}

	payload := payloads.Characteristics{
		Characteristics: characteristics,
		Measurements:    measurements,
		Strains:         strains,
		Species:         species,
	}

	return &payload, nil
}

// Get retrieves a single characteristic
func (c CharacteristicService) Get(id int64, genus string, claims *types.Claims) (types.Entity, *types.AppError) {
	characteristic, err := models.GetCharacteristic(id, genus, claims)
	if err != nil {
		return nil, newJSONError(err, http.StatusInternalServerError)
	}

	strains, strainOpts, err := models.StrainsFromCharacteristicID(id, genus, claims)
	if err != nil {
		return nil, newJSONError(err, http.StatusInternalServerError)
	}

	speciesOpt, err := models.SpeciesOptsFromStrains(*strainOpts)
	if err != nil {
		return nil, newJSONError(err, http.StatusInternalServerError)
	}

	species, err := models.ListSpecies(*speciesOpt, claims)
	if err != nil {
		return nil, newJSONError(err, http.StatusInternalServerError)
	}

	measurements, _, err := models.MeasurementsFromCharacteristicID(id, genus, claims)
	if err != nil {
		return nil, newJSONError(err, http.StatusInternalServerError)
	}

	payload := payloads.Characteristic{
		Characteristic: characteristic,
		Measurements:   measurements,
		Strains:        strains,
		Species:        species,
	}

	return &payload, nil
}

// Update modifies an existing characteristic
func (c CharacteristicService) Update(id int64, e *types.Entity, genus string, claims *types.Claims) *types.AppError {
	payload := (*e).(*payloads.Characteristic)
	payload.Characteristic.UpdatedBy = claims.Sub
	payload.Characteristic.ID = id

	// First, handle Characteristic Type
	id, err := models.InsertOrGetCharacteristicType(payload.Characteristic.CharacteristicType, claims)
	if err != nil {
		return newJSONError(err, http.StatusInternalServerError)
	}

	payload.Characteristic.CanEdit = helpers.CanEdit(claims, payload.Characteristic.CreatedBy)

	payload.Characteristic.CharacteristicTypeID = id

	if err := models.Update(payload.Characteristic.CharacteristicBase); err != nil {
		if err == errors.ErrCharacteristicNotUpdated {
			return newJSONError(err, http.StatusBadRequest)
		}
		if err, ok := err.(types.ValidationError); ok {
			return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity}
		}
		return newJSONError(err, http.StatusInternalServerError)
	}

	strains, strainOpts, err := models.StrainsFromCharacteristicID(id, genus, claims)
	if err != nil {
		return newJSONError(err, http.StatusInternalServerError)
	}

	speciesOpt, err := models.SpeciesOptsFromStrains(*strainOpts)
	if err != nil {
		return newJSONError(err, http.StatusInternalServerError)
	}

	species, err := models.ListSpecies(*speciesOpt, claims)
	if err != nil {
		return newJSONError(err, http.StatusInternalServerError)
	}

	payload.Strains = strains
	// TODO: tack on measurements
	payload.Measurements = nil
	payload.Species = species

	return nil
}

// Create initializes a new characteristic
func (c CharacteristicService) Create(e *types.Entity, genus string, claims *types.Claims) *types.AppError {
	payload := (*e).(*payloads.Characteristic)
	payload.Characteristic.CreatedBy = claims.Sub
	payload.Characteristic.UpdatedBy = claims.Sub

	id, err := models.InsertOrGetCharacteristicType(payload.Characteristic.CharacteristicType, claims)
	if err != nil {
		return newJSONError(err, http.StatusInternalServerError)
	}
	payload.Characteristic.CharacteristicTypeID = id

	if err := models.Create(payload.Characteristic.CharacteristicBase); err != nil {
		if err, ok := err.(types.ValidationError); ok {
			return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity}
		}
		return newJSONError(err, http.StatusInternalServerError)
	}

	characteristic, err := models.GetCharacteristic(payload.Characteristic.ID, genus, claims)
	if err != nil {
		return newJSONError(err, http.StatusInternalServerError)
	}

	payload.Characteristic = characteristic

	return nil
}

// Delete deletes a single characteristic
func (c CharacteristicService) Delete(id int64, genus string, claims *types.Claims) *types.AppError {
	characteristic, err := models.GetCharacteristic(id, genus, claims)
	if err != nil {
		return newJSONError(err, http.StatusInternalServerError)
	}

	if !characteristic.CanEdit {
		return newJSONError(errors.ErrCharacteristicNotDeleted, http.StatusForbidden)
	}

	if err := models.Delete(characteristic.CharacteristicBase); err != nil {
		return newJSONError(err, http.StatusInternalServerError)
	}

	return nil
}