package dev.fyloz.trial.colorrecipesexplorer.model

import dev.fyloz.trial.colorrecipesexplorer.model.validation.NullOrNotBlank
import dev.fyloz.trial.colorrecipesexplorer.model.validation.NullOrSize
import org.springframework.util.Assert
import org.springframework.web.multipart.MultipartFile
import java.util.*
import javax.persistence.*
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
import javax.validation.constraints.Size

private const val MATERIAL_ID_NULL_MESSAGE = "Un identifiant est requis"
private const val MATERIAL_NAME_NULL_MESSAGE = "Un nom est requis"
private const val MATERIAL_INVENTORY_QUANTITY_NULL_MESSAGE = "Une quantité est requise"
private const val MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE = "La quantité doit être supérieure ou égale à 0"
private const val MATERIAL_TYPE_NULL_MESSAGE = "Un type de produit est requis"

@Entity
data class Material(
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        override val id: Long?,

        @Column(unique = true)
        override var name: String,

        var inventoryQuantity: Float,

        val isMixType: Boolean,

        @ManyToOne
        var materialType: MaterialType?
) : NamedModel {
    constructor(name: String, inventoryQuantity: Float, isMixType: Boolean, materialType: MaterialType) : this(null, name, inventoryQuantity, isMixType, materialType)
    constructor() : this(null, "", 0f, false, null)

    override fun equals(other: Any?): Boolean = other is Material && name == other.name
    override fun hashCode(): Int = Objects.hash(name)
}

open class MaterialSaveDto(
        @field:NotBlank(message = MATERIAL_NAME_NULL_MESSAGE)
        val name: String,

        @field:NotNull(message = MATERIAL_INVENTORY_QUANTITY_NULL_MESSAGE)
        @field:Size(min = 0, message = MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE)
        val inventoryQuantity: Float,

        @field:NotNull(message = MATERIAL_TYPE_NULL_MESSAGE)
        val materialType: MaterialType,

        val simdutFile: MultipartFile? = null
) : EntityDto<Material> {
    override fun toEntity(): Material = Material(null, name, inventoryQuantity, false, materialType)
    fun toMaterial() = toEntity()
}

open class MaterialUpdateDto(
        @field:NotNull(message = MATERIAL_ID_NULL_MESSAGE)
        val id: Long,

        @field:NullOrNotBlank(message = MATERIAL_NAME_NULL_MESSAGE)
        val name: String?,

        @field:NullOrSize(min = 0, message = MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE)
        val inventoryQuantity: Float?,

        val materialType: MaterialType?,

        val simdutFile: MultipartFile? = null
) : EntityDto<Material> {
    override fun toEntity(): Material = Material(id, name ?: "", inventoryQuantity
            ?: Float.MIN_VALUE, false, materialType)

    fun toMaterial() = toEntity()
}

data class InventoryMaterial(
        @NotNull val id: Long,
        @NotNull val name: String,
        @NotNull val inventoryQuantity: Float,
        @NotNull val materialTypeName: String,
        @NotNull val lowQuantity: Boolean = false
)

fun Material.toInventoryMaterial(minimumQuantity: Float): InventoryMaterial {
    Assert.notNull(id, "Cannot convert a material without id to an inventory material")
    Assert.notNull(name, "Cannot convert a material without name to an inventory material")
    Assert.notNull(materialType, "Cannot convert a material without a material type to an inventory material")
    return InventoryMaterial(id!!, name, inventoryQuantity, materialType!!.name, inventoryQuantity < minimumQuantity)
}

// ==== DSL ====
fun material(
        id: Long? = null,
        name: String = "name",
        inventoryQuantity: Float = 0f,
        isMixType: Boolean = false,
        materialType: MaterialType? = materialType(),
        op: Material.() -> Unit = {}
) = Material(id, name, inventoryQuantity, isMixType, materialType).apply(op)

fun material(
        material: Material,
        id: Long? = null,
        name: String? = null,
) = Material(id ?: material.id, name ?: material.name, material.inventoryQuantity, material.isMixType, material.materialType)

fun materialSaveDto(
        name: String = "name",
        inventoryQuantity: Float = 0f,
        materialType: MaterialType = materialType(),
        simdutFile: MultipartFile? = null,
        op: MaterialSaveDto.() -> Unit = {}
) = MaterialSaveDto(name, inventoryQuantity, materialType, simdutFile).apply(op)

fun materialUpdateDto(
        id: Long = 0L,
        name: String = "name",
        inventoryQuantity: Float = 0f,
        materialType: MaterialType = materialType(),
        simdutFile: MultipartFile? = null,
        op: MaterialUpdateDto.() -> Unit = {}
) = MaterialUpdateDto(id, name, inventoryQuantity, materialType, simdutFile).apply(op)
