package dev.fyloz.trial.colorrecipesexplorer.service

import com.nhaarman.mockitokotlin2.*
import dev.fyloz.trial.colorrecipesexplorer.model.*
import dev.fyloz.trial.colorrecipesexplorer.repository.RecipeRepository
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class RecipeServiceTest :
    AbstractExternalModelServiceTest<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeService, RecipeRepository>() {
    override val repository: RecipeRepository = mock()
    private val companyService: CompanyService = mock()
    private val mixService: MixService = mock()
    override val service: RecipeService = spy(RecipeServiceImpl(repository, companyService, mixService))

    private val company: Company = company(id = 0L)
    override val entity: Recipe = recipe(id = 0L, name = "recipe", company = company)
    override val anotherEntity: Recipe = recipe(id = 1L, name = "another recipe", company = company)
    override val entitySaveDto: RecipeSaveDto = spy(recipeSaveDto(name = entity.name, companyId = entity.company.id!!))
    override val entityUpdateDto: RecipeUpdateDto = spy(recipeUpdateDto(id = entity.id!!, name = entity.name))

    @Nested
    inner class ExistsByCompany {
        @Test
        fun `returns true when at least one recipe exists for the given company`() {
            whenever(repository.existsByCompany(company)).doReturn(true)

            val found = service.existsByCompany(company)

            assertTrue(found)
        }

        @Test
        fun `returns false when no recipe exists for the given company`() {
            whenever(repository.existsByCompany(company)).doReturn(false)

            val found = service.existsByCompany(company)

            assertFalse(found)
        }
    }

    @Nested
    inner class GetAllByCompany {
        @Test
        fun `returns the recipes with the given company`() {
            val companies = listOf(entity, anotherEntity)
            whenever(repository.findAllByCompany(company)).doReturn(companies)

            val found = service.getAllByCompany(company)

            assertEquals(companies, found)
        }
    }

    @Nested
    inner class SaveDto {
        @Test
        fun `calls and returns save() with the created entity`() {
            whenever(companyService.getById(company.id!!)).doReturn(company)
            saveDtoTest(entity, entitySaveDto, service)
        }
    }

    @Nested
    inner class UpdatePublicDate {
        @Test
        fun `calls update with the updated note`() {
            val publicDataDto = recipePublicDataDto(id = entity.id!!, note = "newNote", mixesLocation = null)
            val expected = entity.apply { note = publicDataDto.note!! }
            doReturn(entity).whenever(service).getById(entity.id!!)
            doReturn(expected).whenever(service).update(expected)

            service.updatePublicData(publicDataDto)

            verify(service).update(expected)
        }

        @Test
        fun `calls mixService_updateLocation() with every mix and locations`() {
            val mix = mix(id = 0L)
            val mixRecipe = entity.apply { mixes.add(mix) }
            val location = "location"
            val mixLocation = mapOf(mix.id!! to location)
            val publicDataDto = recipePublicDataDto(id = mixRecipe.id!!, note = null, mixesLocation = mixLocation)
            doReturn(mixRecipe).whenever(service).getById(mixRecipe.id!!)

            service.updatePublicData(publicDataDto)

            verify(mixService).updateLocation(mix, location)
        }
    }

    @Nested
    inner class AddMix {
        @Test
        fun `adds the given mix to the given recipe and updates it`() {
            val mix = mix(id = 0L)
            val recipe = recipe(id = 0L, mixes = mutableListOf())

            doAnswer { it.arguments[0] }.whenever(service).update(any<Recipe>())

            val found = service.addMix(recipe, mix)

            verify(service).update(any<Recipe>())

            assertEquals(recipe.id, found.id)
            assertTrue(found.mixes.contains(mix))
        }
    }

    @Nested
    inner class RemoveMix {
        @Test
        fun `removes the given mix from its recipe and updates it`() {
            val recipe = recipe(id = 0L, mixes = mutableListOf())
            val mix = mix(id = 0L, recipe = recipe)
            recipe.mixes.add(mix)

            doAnswer { it.arguments[0] }.whenever(service).update(any<Recipe>())

            val found = service.removeMix(mix)

            verify(service).update(any<Recipe>())

            assertEquals(recipe.id, found.id)
            assertFalse(found.mixes.contains(mix))
        }
    }
}
