package dev.jriley.hackernews.data

import com.nhaarman.mockito_kotlin.*
import dev.jriley.hackernews.services.HackerNewsService
import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.Single
import io.reactivex.schedulers.TestScheduler
import io.reactivex.subjects.BehaviorSubject
import org.junit.Test
import test
import java.util.*

class StoryRepositoryTest {

    private lateinit var testObject: StoryRepository
    private val storyData: StoryData = mock()
    private val hackerNewsService: HackerNewsService = mock()
    private val ioScheduler: TestScheduler = TestScheduler()
    private val random = Random()

    @Test
    fun isLoadedDoNotCallService() {
        whenever(storyData.isLoaded()).thenReturn(Single.just(true))
        testObject = StoryRepository(storyData, hackerNewsService, ioScheduler)

        val testObserver = testObject.isLoaded().test()

        testObserver.apply {
            assertNoValues()
            assertNotComplete()
            assertNoErrors()
        }

        ioScheduler.triggerActions()

        testObserver.apply {
            assertValues(true)
            assertComplete()
            assertNoErrors()
        }

        verifyZeroInteractions(hackerNewsService)
    }

    @Test
    fun loadBestStories() {
        val story1 = Story.test(random.nextLong())
        val expectedInsert = Story(story1, StoryTypes.BEST)
        val expectedIdList = listOf(story1.id)
        whenever(storyData.isLoaded(any())).thenReturn(false)
        whenever(storyData.insert(expectedInsert)).thenReturn(Single.just(story1.id))
        whenever(hackerNewsService.best).thenReturn(Single.just(expectedIdList))
        whenever(hackerNewsService.getStory("${story1.id}")).thenReturn(Single.just(story1))
        testObject = StoryRepository(storyData, hackerNewsService, ioScheduler)

        val testObserver = testObject.loadBest().test()

        testObserver.apply {
            assertNoValues()
            assertNotComplete()
            assertNoErrors()
        }

        ioScheduler.triggerActions()

        testObserver.apply {
            assertValues(expectedIdList)
            assertComplete()
            assertNoErrors()
        }
    }

    @Test
    fun loadTopStories() {
        val story1 = Story.test(random.nextLong())
        val expectedInsert = Story(story1, StoryTypes.TOP)
        val expectedIdList = listOf(story1.id)
        whenever(storyData.isLoaded(any())).thenReturn(false)
        whenever(storyData.insert(expectedInsert)).thenReturn(Single.just(story1.id))
        whenever(hackerNewsService.top).thenReturn(Single.just(expectedIdList))
        whenever(hackerNewsService.getStory("${story1.id}")).thenReturn(Single.just(story1))
        testObject = StoryRepository(storyData, hackerNewsService, ioScheduler)

        val testObserver = testObject.loadTop().test()

        testObserver.apply {
            assertNoValues()
            assertNotComplete()
            assertNoErrors()
        }

        ioScheduler.triggerActions()

        testObserver.apply {
            assertValues(expectedIdList)
            assertComplete()
            assertNoErrors()
        }
    }

    @Test
    fun loadNewStories() {
        val expectedStoryList: List<Story> = listOf(Story.test(random.nextLong()))
        val story1 = expectedStoryList.first()
        val expectedIdList = listOf(story1.id)
        whenever(storyData.isLoaded(any())).thenReturn(false)
        whenever(storyData.insert(story1)).thenReturn(Single.just(story1.id))
        whenever(hackerNewsService.new).thenReturn(Single.just(expectedIdList))
        whenever(hackerNewsService.getStory("${story1.id}")).thenReturn(Single.just(story1))
        testObject = StoryRepository(storyData, hackerNewsService, ioScheduler)

        val testObserver = testObject.loadNew().test()

        testObserver.apply {
            assertNoValues()
            assertNotComplete()
            assertNoErrors()
        }

        ioScheduler.triggerActions()

        testObserver.apply {
            assertValues(expectedIdList)
            assertComplete()
            assertNoErrors()
        }
    }

    @Test
    fun doNotLoadStoriesAlreadyInDB() {
        val expectedIdList = listOf(random.nextLong())
        whenever(storyData.isLoaded(any())).thenReturn(true)
        whenever(hackerNewsService.best).thenReturn(Single.just(expectedIdList))
        testObject = StoryRepository(storyData, hackerNewsService, ioScheduler)

        val testObserver = testObject.loadBest().test()

        testObserver.apply {
            assertNoValues()
            assertNotComplete()
            assertNoErrors()
        }

        ioScheduler.triggerActions()

        testObserver.apply {
            assertValues(listOf())
            assertComplete()
            assertNoErrors()
        }

        verify(storyData, never()).insert(any())
        verify(hackerNewsService, never()).getStory(any())
    }


    @Test
    fun flowFromRepo() {
        val expectedStoryList = listOf(Story.test())
        val behaviorSubject = BehaviorSubject.create<List<Story>>()
        val flowable: Flowable<List<Story>> = behaviorSubject.toFlowable(BackpressureStrategy.BUFFER)
        whenever(storyData.storyList()).thenReturn(flowable)
        testObject = StoryRepository(storyData, hackerNewsService, ioScheduler)

        testObject.flowBest().test().apply {
            assertNoErrors()
            assertNotComplete()
            assertNoValues()
        }

        behaviorSubject.onNext(expectedStoryList)

        testObject.flowBest().test().apply {
            assertNoErrors()
            assertNotComplete()
            assertValues(expectedStoryList)
        }

    }

    @Test
    fun updateStory() {
        val expectedStory = Story.test()
        whenever(storyData.update(expectedStory)).thenReturn(Single.just(1))
        testObject = StoryRepository(storyData, hackerNewsService, ioScheduler)

        val testObserver = testObject.bookmarkStory(expectedStory).test().apply {
            assertNoErrors()
            assertNotComplete()
            assertNoValues()
        }

        ioScheduler.triggerActions()

       testObserver.apply {
            assertNoErrors()
            assertComplete()
            assertValue(1)
        }
    }
}