package dev.fyloz.trial.colorrecipesexplorer.exception

import com.fasterxml.jackson.annotation.JsonProperty
import dev.fyloz.trial.colorrecipesexplorer.exception.model.*
import org.springframework.context.annotation.Profile
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.validation.FieldError
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.context.request.WebRequest
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler

abstract class RestException(val httpStatus: HttpStatus) : RuntimeException() {
    abstract val exceptionMessage: String
    abstract fun buildBody(): RestExceptionBody

    override val message: String by lazy { exceptionMessage }

    open inner class RestExceptionBody(val status: Int = httpStatus.value(), @JsonProperty("message") val message: String = exceptionMessage)
}

@ControllerAdvice
@Profile("rest")
class RestResponseEntityExceptionHandler : ResponseEntityExceptionHandler() {
    @ExceptionHandler(ModelException::class)
    fun handleModelExceptions(exception: ModelException, request: WebRequest): ResponseEntity<Any> {
        return handleRestExceptions(exception.toRestException(), request)
    }

    @ExceptionHandler(RestException::class)
    fun handleRestExceptions(exception: RestException, request: WebRequest): ResponseEntity<Any> {
        return handleExceptionInternal(exception, exception.buildBody(), HttpHeaders(), exception.httpStatus, request)
    }

    override fun handleMethodArgumentNotValid(ex: MethodArgumentNotValidException, headers: HttpHeaders, status: HttpStatus, request: WebRequest): ResponseEntity<Any> {
        val errors = hashMapOf<String, String>()
        ex.bindingResult.allErrors.forEach {
            val fieldName = (it as FieldError).field
            val errorMessage = it.defaultMessage!!
            errors[fieldName] = errorMessage
        }
        return ResponseEntity(errors, headers, status)
    }
}

fun ModelException.toRestException(): RestException {
    return when (this) {
        is EntityAlreadyExistsException -> EntityAlreadyExistsRestException(requestedId)
        is EntityNotFoundException -> EntityNotFoundRestException(requestedId)
        else -> throw UnsupportedOperationException("Cannot convert ${this::class.simpleName} to REST exception")
    }
}
