package dev.fyloz.trial.colorrecipesexplorer.service

import dev.fyloz.trial.colorrecipesexplorer.model.*
import dev.fyloz.trial.colorrecipesexplorer.model.validation.isNotNullAndNotBlank
import dev.fyloz.trial.colorrecipesexplorer.model.validation.or
import dev.fyloz.trial.colorrecipesexplorer.repository.RecipeRepository
import org.springframework.stereotype.Service
import kotlin.contracts.ExperimentalContracts

interface RecipeService : ExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeRepository> {
    /** Checks if one or more recipes have the given [company]. */
    fun existsByCompany(company: Company): Boolean

    /** Gets all recipes with the given [company]. */
    fun getAllByCompany(company: Company): Collection<Recipe>

    /** Updates the public data of a recipe with the given [publicDateDto]. */
    fun updatePublicData(publicDataDto: RecipePublicDataDto)

    /** Adds the given [mix] to the given [recipe]. */
    fun addMix(recipe: Recipe, mix: Mix): Recipe

    /** Removes the given [mix] from its recipe. */
    fun removeMix(mix: Mix): Recipe
}

@Service
class RecipeServiceImpl(
    recipeRepository: RecipeRepository,
    val companyService: CompanyService,
    val mixService: MixService
) :
    AbstractExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeRepository>(recipeRepository),
    RecipeService {
    override fun existsByCompany(company: Company): Boolean = repository.existsByCompany(company)
    override fun getAllByCompany(company: Company): Collection<Recipe> = repository.findAllByCompany(company)

    override fun save(entity: RecipeSaveDto): Recipe {
        // TODO checks if name is unique in the scope of the [company]
        return save(with(entity) {
            recipe(
                name = name,
                description = description,
                sample = sample,
                approbationDate = approbationDate,
                remark = remark ?: "",
                company = companyService.getById(companyId)
            )
        })
    }

    @ExperimentalContracts
    override fun update(entity: RecipeUpdateDto): Recipe {
        val persistedRecipe by lazy { getById(entity.id) }

        return update(with(entity) {
            recipe(
                id = id,
                name = name.or(persistedRecipe.name),
                description = description.or(persistedRecipe.description),
                sample = if (sample != null && sample >= 0) sample else persistedRecipe.sample,
                approbationDate = approbationDate ?: persistedRecipe.approbationDate,
                remark = remark.or(persistedRecipe.remark),
                note = persistedRecipe.note,
                company = persistedRecipe.company,
                mixes = persistedRecipe.mixes,
                steps = steps?.toMutableList() ?: persistedRecipe.steps
            )
        })
    }

    @ExperimentalContracts
    override fun updatePublicData(publicDataDto: RecipePublicDataDto) {
        val recipe = getById(publicDataDto.id)

        if (isNotNullAndNotBlank(publicDataDto.note)) {
            update(recipe.apply { note = publicDataDto.note })
        }

        with(publicDataDto.mixesLocation) {
            if (!isNullOrEmpty()) {
                // Map each mix ID to their mix in the recipe
                map { recipe.mixes.firstOrNull { mix -> mix.id == it.key } to it.value }
                    // Remove pairs with a null mix
                    .filter { it.first != null }
                    .forEach { mixService.updateLocation(it.first!!, it.second) }
            }
        }
    }

    override fun addMix(recipe: Recipe, mix: Mix) =
        update(recipe.apply { mixes.add(mix) })

    override fun removeMix(mix: Mix): Recipe =
        update(mix.recipe.apply { mixes.remove(mix) })
}
