package com.isidroid.spot

import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import com.isidroid.spot.model.RichSpot
import com.isidroid.spot.model.Spot
import com.isidroid.spot.data.source.local.dao.PrintProfileDao
import com.isidroid.spot.data.source.local.dao.SpotDao
import com.isidroid.spot.data.source.local.source.PrintProfileLocalSource
import com.isidroid.spot.data.source.local.source.SpotLocalSource
import com.isidroid.spot.data.source.remote.ApiSpot
import com.isidroid.spot.data.source.remote.source.SpotNetworkSource
import com.isidroid.spot.repository.SpotRepositoryImpl
import com.isidroid.test_utils.MockWebServerWrapper
import com.isidroid.test_utils.createApi
import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class ApiSpotTest {
    @Mock private lateinit var mockContext: Context
    private lateinit var mockWebServer: MockWebServer
    private lateinit var apiSpot: ApiSpot
    private lateinit var spotRepository: SpotRepositoryImpl
    private lateinit var spotLocalSource: SpotLocalSource
    private lateinit var spotNetworkSource: SpotNetworkSource
    private lateinit var printProfileLocalSource: PrintProfileLocalSource

    private val mockWrapper = MockWebServerWrapper { path, method, body ->
        when {
            path == "/api/spot/locate" -> "locate_spots"
            path == "/api/spot/find/1" -> "find_spot"
            else -> ""
        }
    }

    @Before
    fun setup() {
        val spotDao = mock(SpotDao::class.java)
        val printProfileDao = mock(PrintProfileDao::class.java)

        mockContext = InstrumentationRegistry.getInstrumentation().targetContext
        mockWebServer = mockWrapper.create()
        apiSpot = createApi(mockWebServer, ApiSpot::class.java)
        spotNetworkSource = SpotNetworkSource(apiSpot)
        spotLocalSource = SpotLocalSource(spotDao)
        printProfileLocalSource = PrintProfileLocalSource(printProfileDao)
        spotRepository = SpotRepositoryImpl(spotLocalSource = spotLocalSource, spotNetworkSource = spotNetworkSource, printProfileLocalSource = printProfileLocalSource)
    }

    @After
    fun shutdown() {
        mockWebServer.shutdown()
    }

    @Test
    fun `check mapper for spot and profiles`() {
        runBlocking {
            val result = spotRepository.locateSpots(lat = 0.0, lng = 0.0, distance = 50)
            val expectedSize = 6
            val expectedSpotIds = listOf(8, 7, 1, 10, 13, 2).sorted().map { it.toString() }
            val actualSpotIds = result.map { it.spot.id.toIntOrNull() ?: 0 }.sorted().map { it.toString() }
            val printProfiles = result.map { it.printProfiles }.flatten()

            val spotId = "1"
            val spot = result.find { it.spot.id == spotId } ?: throw Exception("Spot not found")
            val profileBW = printProfiles.find { it.id == "11" } ?: throw Exception("Profile BR not found")
            val spotNoProfiles = result.find { it.spot.id == "10" }?: throw Exception("Spot without profiles not found")

            Assert.assertNotNull("method `findSpots` must be not null, otherwise it's mapping error", result)
            Assert.assertEquals("response contains $expectedSize spots", expectedSize, result.size)
            Assert.assertEquals("sorted list of spot ids it not correct", expectedSpotIds, actualSpotIds)

            assertSpot(spot)

            // check grayscale
            Assert.assertEquals("profileBW should be Black/White", true, profileBW.grayscale)

            // check spot without profiles
            Assert.assertNotNull("spot without profiles", spotNoProfiles)
        }
    }

    @Test
    fun `find spot by code`() = runBlocking {
        val result = spotRepository.findRichSpot("1")

        Assert.assertNotNull("failed mapper", result)
        assertSpot(result)
    }

    private fun assertSpot(spot: RichSpot?){
        val profile1 = spot?.printProfiles?.find { it.id == "2" } ?: throw Exception("Profile not found")

        // spot and profiles assertions
        Assert.assertEquals("number of profiles is not correct", 7, spot.printProfiles.size)
        Assert.assertEquals("check profile.id", false, profile1.id.isBlank())
        Assert.assertEquals("check profile.name", false, profile1.name.isBlank())
        Assert.assertNotEquals("check profile.width", 0, profile1.width)
        Assert.assertNotEquals("check profile.height", 0, profile1.height)
        Assert.assertNotEquals("check profile.printWidth", 0, profile1.printWidth)
        Assert.assertNotEquals("check profile.printHeight", 0, profile1.printHeight)
        Assert.assertNotEquals("check profile.marginTop", 0, profile1.marginTop)
        Assert.assertNotEquals("check profile.marginLeft", 0, profile1.marginLeft)
        Assert.assertNotEquals("check profile.cost", 0, profile1.cost)
        Assert.assertEquals("check profile.grayscale", false, profile1.grayscale)

    }
}