package com.ing.baker.tutorials

import akka.actor.ActorSystem
import com.ing.baker.compiler.RecipeCompiler
import com.ing.baker.runtime.akka.AkkaBaker
import com.ing.baker.runtime.javadsl.EventInstance
import com.ing.baker.runtime.javadsl.InteractionInstance
import com.ing.baker.tutorials.events.ManufactureGoodsEvents.GoodsManufactured
import com.ing.baker.tutorials.events.SendInvoiceEvents.InvoiceSent
import com.ing.baker.tutorials.events.SensoryEvents
import com.ing.baker.tutorials.events.ShipGoodsEvents.GoodsShipped
import com.ing.baker.tutorials.events.ValidateOrderEvents.OrderValid
import com.ing.baker.tutorials.interactions.ManufactureGoods
import com.ing.baker.tutorials.interactions.SendInvoice
import com.ing.baker.tutorials.interactions.ShipGoods
import com.ing.baker.tutorials.interactions.ValidateOrder
import com.ing.baker.tutorials.model.CustomerInfo
import guru.nidi.graphviz.engine.Format
import guru.nidi.graphviz.engine.Graphviz
import guru.nidi.graphviz.parse.Parser
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito.*
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import scala.collection.mutable.ArraySeq
import scala.concurrent.Await
import scala.concurrent.duration.Duration
import java.io.File
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

class WebShopRecipeTest {
    private val recipe = WebShopRecipe.getRecipe()
    private val mockValidateOrder = mock<ValidateOrder>()
    private val mockManufactureGoods = mock<ManufactureGoods>()
    private val mockShipGoods = mock<ShipGoods>()
    private val mockSendInvoice = mock<SendInvoice>()

    private val actorSystem = ActorSystem.create("WebShop")
    private val baker = AkkaBaker.javaLocalDefault(actorSystem, listOf(InteractionInstance.from(mockValidateOrder),
        InteractionInstance.from(mockManufactureGoods),
        InteractionInstance.from(mockShipGoods),
        InteractionInstance.from(mockSendInvoice)))

    private var recipeId: String

    private val orderId = "ORD-123456"
    private val customerInfo = CustomerInfo("John Doe, Amsterdam")
    private val goods = "this is the product"

    init {
        val compiledRecipe = RecipeCompiler.compileRecipe(recipe)
        recipeId = baker.addRecipe(compiledRecipe, true).get()
    }

    @AfterEach
    fun stopActorSystem() {
        Await.ready(actorSystem.terminate(), Duration.apply(20, TimeUnit.SECONDS))
    }

    @Test
    fun shouldVisualiseTheRecipe() {
        val compiledRecipe = RecipeCompiler.compileRecipe(recipe)
        val visualRecipe = compiledRecipe.recipeVisualization
        saveVisualizationAsSvg(visualRecipe)
    }

    @Test
    fun shouldHaveNoValidationErrors() {
        val compiledRecipe = RecipeCompiler.compileRecipe(recipe)
        assertEquals(compiledRecipe.validationErrors(), ArraySeq.empty())
    }

    @Test
    fun shouldBakeTheRecipe() {
        val processId = UUID.randomUUID().toString()
        baker.bake(recipeId, processId)
        val accumulatedState = baker.getIngredients(processId)
        assertNotNull(accumulatedState)
    }

    @Test
    fun shouldExecuteHappyFowCorrectly() {
        setupMocks()

        val processId = UUID.randomUUID().toString()
        baker.bake(recipeId, processId)

        baker.fireEventAndResolveWhenCompleted(processId, EventInstance.from(SensoryEvents.CustomerInfoReceived(customerInfo))).get()
        verify(mockValidateOrder, never()).apply(anyString())
        verify(mockManufactureGoods, never()).apply(anyString())
        verify(mockShipGoods, never()).apply(goods, customerInfo)
        verify(mockSendInvoice, never()).apply(customerInfo)

        baker.fireEventAndResolveWhenCompleted(processId, EventInstance.from(SensoryEvents.OrderPlaced(orderId))).get()
        verify(mockValidateOrder).apply(orderId)
        verify(mockManufactureGoods, never()).apply(anyString())
        verify(mockShipGoods, never()).apply(goods, customerInfo)
        verify(mockSendInvoice, never()).apply(customerInfo)

        baker.fireEventAndResolveWhenCompleted(processId, EventInstance.from(SensoryEvents.PaymentMade())).get()
        verify(mockManufactureGoods).apply(orderId)
        verify(mockShipGoods).apply(goods, customerInfo)
        verify(mockSendInvoice).apply(customerInfo)

        verify(mockValidateOrder, times(1)).apply(orderId)
        verify(mockManufactureGoods, times(1)).apply(orderId)
        verify(mockShipGoods, times(1)).apply(goods, customerInfo)
        verify(mockSendInvoice, times(1)).apply(customerInfo)
    }

    private fun saveVisualizationAsSvg(dotGraph: String) {
        val file = File("./target/" + "web-shop-recipe.svg")
        val g = Parser.read(dotGraph)
        Graphviz.fromGraph(g).render(Format.SVG).toFile(file)
    }

    private fun setupMocks() {
        `when`(mockValidateOrder.apply(orderId)).thenReturn(OrderValid())
        `when`(mockManufactureGoods.apply(orderId)).thenReturn(GoodsManufactured(goods))
        `when`(mockShipGoods.apply(goods, customerInfo)).thenReturn(GoodsShipped())
        `when`(mockSendInvoice.apply(customerInfo)).thenReturn(InvoiceSent())
    }

}