package dev.fyloz.trial.colorrecipesexplorer.rest

import dev.fyloz.trial.colorrecipesexplorer.model.EntityDto
import dev.fyloz.trial.colorrecipesexplorer.model.Model
import dev.fyloz.trial.colorrecipesexplorer.service.ModelService
import dev.fyloz.trial.colorrecipesexplorer.service.Service
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import java.net.URI
import javax.validation.Valid

interface RestApiController<E, S : EntityDto<E>, U : EntityDto<E>> {
    @ResponseStatus(HttpStatus.OK)
    fun getAll(): ResponseEntity<Iterable<E>>

    @ResponseStatus(HttpStatus.CREATED)
    fun save(entity: S): ResponseEntity<E>

    @ResponseStatus(HttpStatus.NO_CONTENT)
    fun update(entity: U): ResponseEntity<Void>

    @ResponseStatus(HttpStatus.NO_CONTENT)
    fun delete(entity: E): ResponseEntity<Void>
}

interface RestModelApiController<E : Model, S : EntityDto<E>, U : EntityDto<E>> : RestApiController<E, S, U> {
    @ResponseStatus(HttpStatus.OK)
    fun getById(id: Long): ResponseEntity<E>

    @ResponseStatus(HttpStatus.NO_CONTENT)
    fun deleteById(id: Long): ResponseEntity<Void>
}

abstract class AbstractRestApiController<E, N : EntityDto<E>, U : EntityDto<E>, S : Service<E, N, U, *>>(val service: S, protected val controllerPath: String) :
        RestApiController<E, N, U> {
    protected abstract fun getEntityId(entity: E): Any?

    @GetMapping
    override fun getAll(): ResponseEntity<Iterable<E>> = ResponseEntity.ok(service.getAll())

    @PostMapping
    override fun save(@Valid @RequestBody entity: N): ResponseEntity<E> {
        val saved = service.save(entity)
        return ResponseEntity
                .created(URI("$controllerPath/${getEntityId(saved)}"))
                .body(saved)
    }

    @PutMapping
    override fun update(@Valid @RequestBody entity: U): ResponseEntity<Void> {
        service.update(entity)
        return ResponseEntity
                .noContent()
                .build()
    }

    @DeleteMapping
    override fun delete(@Valid @RequestBody entity: E): ResponseEntity<Void> {
        service.delete(entity)
        return ResponseEntity
                .noContent()
                .build()
    }
}

abstract class AbstractRestModelApiController<E : Model, N : EntityDto<E>, U : EntityDto<E>, S : ModelService<E, N, U, *>>(service: S, controllerPath: String) :
        AbstractRestApiController<E, N, U, S>(service, controllerPath), RestModelApiController<E, N, U> {
    override fun getEntityId(entity: E) = entity.id

    @GetMapping("{id}")
    override fun getById(@PathVariable id: Long): ResponseEntity<E> = ResponseEntity.ok(service.getById(id))

    @DeleteMapping("{id}")
    override fun deleteById(@PathVariable id: Long): ResponseEntity<Void> {
        service.deleteById(id)
        return ResponseEntity
                .noContent()
                .build()
    }
}
