package dev.fyloz.trial.colorrecipesexplorer.service

import dev.fyloz.trial.colorrecipesexplorer.model.*
import dev.fyloz.trial.colorrecipesexplorer.repository.MaterialRepository
import dev.fyloz.trial.colorrecipesexplorer.service.files.SimdutService
import dev.fyloz.trial.colorrecipesexplorer.service.model.MixMaterialJavaService
import io.jsonwebtoken.lang.Assert
import org.springframework.stereotype.Service

interface MaterialService :
    ExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialRepository> {
    /** Checks if a material with the given [materialType] exists. */
    fun existsByMaterialType(materialType: MaterialType): Boolean

    /** Checks if the given [material] is used by one or more mixes. */
    fun isLinkedToMixes(material: Material): Boolean

    /** Checks if the material with the given [id] has a SIMDUT file. */
    fun hasSimdut(id: Long): Boolean

    /** Gets the SIMDUT file of the material with the given [id]. */
    fun getSimdut(id: Long): ByteArray

    /** Gets all materials that are not a mix type. */
    fun getAllNotMixType(): Collection<Material>
}

@Service
class MaterialServiceImpl(
    materialRepository: MaterialRepository,
    val mixQuantityService: MixMaterialService,
    val simdutService: SimdutService
) :
    AbstractExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialRepository>(
        materialRepository
    ),
    MaterialService {
    override fun existsByMaterialType(materialType: MaterialType): Boolean =
        repository.existsByMaterialType(materialType)

    override fun isLinkedToMixes(material: Material): Boolean = mixQuantityService.existsByMaterial(material)
    override fun hasSimdut(id: Long): Boolean = simdutService.exists(getById(id))
    override fun getSimdut(id: Long): ByteArray = simdutService.read(getById(id))
    override fun getAllNotMixType(): Collection<Material> = getAll().filter { !it.isMixType }

    override fun save(entity: MaterialSaveDto): Material =
        save(entity.toMaterial()).apply {
            if (entity.simdutFile != null && !entity.simdutFile.isEmpty) simdutService.write(this, entity.simdutFile)
        }

    override fun update(entity: MaterialUpdateDto): Material {
        val persistedMaterial by lazy {
            getById(entity.id).apply { assertPersistedMaterial(this) }
        }
        assertMaterialType(entity.materialType)

        return update(with(entity) {
            material(
                id = id,
                name = if (name != null && name.isNotBlank()) name else persistedMaterial.name,
                inventoryQuantity = if (inventoryQuantity != null && inventoryQuantity != Float.MIN_VALUE) inventoryQuantity else persistedMaterial.inventoryQuantity,
                isMixType = persistedMaterial.isMixType,
                materialType = materialType ?: persistedMaterial.materialType!!
            )
        }).apply {
            if (entity.simdutFile != null && !entity.simdutFile.isEmpty) simdutService.update(entity.simdutFile, this)
        }
    }

    private fun assertMaterialType(materialType: MaterialType?) {
        Assert.notNull(materialType, "A persisted material has a null material type")
    }

    private fun assertPersistedMaterial(material: Material) {
        Assert.notNull(material.name, "The persisted material with the id ${material.id} has a null name")
    }
}
