package dev.fyloz.trial.colorrecipesexplorer.service

import com.nhaarman.mockitokotlin2.*
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityAlreadyExistsRestException
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.MixQuantityService
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.springframework.mock.web.MockMultipartFile
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class MaterialServiceTest : AbstractNamedModelServiceTest<Material, MaterialSaveDto, MaterialUpdateDto, MaterialService, MaterialRepository>() {
    private val mixQuantityService: MixQuantityService = mock()
    private val simdutService: SimdutService = mock()
    override val repository: MaterialRepository = mock()
    override val service: MaterialService = spy(MaterialServiceImpl(repository, mixQuantityService, simdutService))

    override val entity: Material = material(id = 0L, name = "material")
    override val anotherEntity: Material = material(id = 1L, name = "another material")
    override val entityWithEntityName: Material = material(id = 2L, name = "material")
    override val entitySaveDto: MaterialSaveDto = spy(materialSaveDto())
    override val entityUpdateDto: MaterialUpdateDto = spy(materialUpdateDto(id = 0L))

    @AfterEach
    override fun afterEach() {
        reset(mixQuantityService, simdutService, entitySaveDto, entityUpdateDto)
        super.afterEach()
    }

    @Nested
    inner class ExistsByMaterialType {
        private val materialType = materialType()

        @Test
        fun `returns true when a material with the given material type exists in the repository`() {
            whenever(repository.existsByMaterialType(materialType)).doReturn(true)

            val found = service.existsByMaterialType(materialType)

            assertTrue(found)
        }

        @Test
        fun `returns false when no material with the given material type exists in the repository`() {
            whenever(repository.existsByMaterialType(materialType)).doReturn(false)

            val found = service.existsByMaterialType(materialType)

            assertFalse(found)
        }
    }

    @Nested
    inner class IsLinkedToMixes {
        @Test
        fun `returns true when a mix contains the given material in the repository`() {
            whenever(mixQuantityService.existsByMaterial(entity)).doReturn(true)

            val found = service.isLinkedToMixes(entity)

            assertTrue(found)
        }

        @Test
        fun `returns false when no mix contains the given material in the repository`() {
            whenever(mixQuantityService.existsByMaterial(entity)).doReturn(false)

            val found = service.isLinkedToMixes(entity)

            assertFalse(found)
        }
    }

    @Nested
    inner class GetAllNotMixType {
        @Test
        fun `returns a list containing every material that are not a mix type`() {
            val mixTypeMaterial = material(name = "mix type material", isMixType = true)
            val materialList = listOf(entity, mixTypeMaterial)

            doReturn(materialList).whenever(service).getAll()

            val found = service.getAllNotMixType()

            assertTrue(found.contains(entity))
            assertFalse(found.contains(mixTypeMaterial))
        }
    }

    @Nested
    inner class Save {
        @Test
        fun `throws EntityAlreadyExistsRestException when a material with the given name exists in the repository`() {
            doReturn(true).whenever(service).existsByName(entity.name)

            val exception = assertThrows<EntityAlreadyExistsRestException> { service.save(entity) }
            assertEquals(entity.name, exception.value)
        }
    }

    @Nested
    inner class SaveDto {
        @Test
        fun `calls simdutService_write() with the saved entity`() {
            val mockMultipartFile = spy(MockMultipartFile("simdut", byteArrayOf()))
            val materialSaveDto = spy(materialSaveDto(simdutFile = mockMultipartFile))

            doReturn(false).whenever(mockMultipartFile).isEmpty
            doReturn(entity).whenever(materialSaveDto).toEntity()
            doReturn(entity).whenever(service).save(entity)

            service.save(materialSaveDto)

            verify(simdutService).write(entity, mockMultipartFile)
        }
    }

    @Nested
    inner class Update {
        @Test
        fun `throws EntityAlreadyExistsRestException when another material with the updated name exists in the repository`() {
            val material = material(id = 0L, name = "name")
            val anotherMaterial = material(id = 1L, name = "name")

            whenever(repository.findByName(material.name)).doReturn(anotherMaterial)
            doReturn(entity).whenever(service).getById(material.id!!)

            val exception = assertThrows<EntityAlreadyExistsRestException> { service.update(material) }
            assertEquals(material.name, exception.value)
        }
    }

    @Nested
    inner class UpdateDto {
        @Test
        fun `calls simdutService_update() with the updated entity`() {
            val mockSimdutFile = MockMultipartFile("simdut", byteArrayOf())
            val materialUpdateDto = spy(materialUpdateDto(id = 0L, simdutFile = mockSimdutFile))

            doReturn(entity).whenever(service).update(entity)
            doReturn(entity).whenever(materialUpdateDto).toEntity()

            service.update(materialUpdateDto)

            verify(simdutService).update(mockSimdutFile, entity)
        }
    }
}
