/**
 * Nextcloud Android client application
 *
 * @author Chris Narkiewicz
 * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.nextcloud.client.files.downloader

import android.content.ComponentName
import android.content.Context
import com.nextcloud.client.account.MockUser
import com.owncloud.android.datamodel.OCFile
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import io.mockk.verify
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

class DownloaderConnectionTest {

    lateinit var connection: DownloaderConnection

    @MockK
    lateinit var context: Context

    @MockK
    lateinit var firstDownloadListener: (Download) -> Unit

    @MockK
    lateinit var secondDownloadListener: (Download) -> Unit

    @MockK
    lateinit var firstStatusListener: (Downloader.Status) -> Unit

    @MockK
    lateinit var secondStatusListener: (Downloader.Status) -> Unit

    @MockK
    lateinit var binder: DownloaderService.Binder

    val file get() = OCFile("/path")
    val componentName = ComponentName("", DownloaderService::class.java.simpleName)
    val user = MockUser()

    @Before
    fun setUp() {
        MockKAnnotations.init(this, relaxed = true)
        connection = DownloaderConnection(context, user)
    }

    @Test
    fun listeners_are_set_after_connection() {
        // GIVEN
        //      not connected
        //      listener is added
        connection.registerDownloadListener(firstDownloadListener)
        connection.registerDownloadListener(secondDownloadListener)

        // WHEN
        //      service is bound
        connection.onServiceConnected(componentName, binder)

        // THEN
        //      all listeners are passed to the service
        val listeners = mutableListOf<(Download) -> Unit>()
        verify { binder.registerDownloadListener(capture(listeners)) }
        assertEquals(listOf(firstDownloadListener, secondDownloadListener), listeners)
    }

    @Test
    fun listeners_are_set_immediately_when_connected() {
        // GIVEN
        //      service is bound
        connection.onServiceConnected(componentName, binder)

        // WHEN
        //      listeners are added
        connection.registerDownloadListener(firstDownloadListener)

        // THEN
        //      listener is forwarded to service
        verify { binder.registerDownloadListener(firstDownloadListener) }
    }

    @Test
    fun listeners_are_removed_when_unbinding() {
        // GIVEN
        //      service is bound
        //      service has some listeners
        connection.onServiceConnected(componentName, binder)
        connection.registerDownloadListener(firstDownloadListener)
        connection.registerDownloadListener(secondDownloadListener)

        // WHEN
        //      service unbound
        connection.unbind()

        // THEN
        //      listeners removed from service
        verify { binder.removeDownloadListener(firstDownloadListener) }
        verify { binder.removeDownloadListener(secondDownloadListener) }
    }

    @Test
    fun missed_updates_are_delivered_on_connection() {
        // GIVEN
        //      not bound
        //      has listeners
        //      download is scheduled and is progressing
        connection.registerDownloadListener(firstDownloadListener)
        connection.registerDownloadListener(secondDownloadListener)

        val request1 = Request(user, file)
        connection.download(request1)
        val download1 = Download(request1.uuid, DownloadState.RUNNING, 50, request1.file, request1)

        val request2 = Request(user, file)
        connection.download(request2)
        val download2 = Download(request2.uuid, DownloadState.RUNNING, 50, request2.file, request1)

        every { binder.getDownload(request1.uuid) } returns download1
        every { binder.getDownload(request2.uuid) } returns download2

        // WHEN
        //      service is bound
        connection.onServiceConnected(componentName, binder)

        // THEN
        //      listeners receive current download state for pending downloads
        val firstListenerNotifications = mutableListOf<Download>()
        verify { firstDownloadListener(capture(firstListenerNotifications)) }
        assertEquals(listOf(download1, download2), firstListenerNotifications)

        val secondListenerNotifications = mutableListOf<Download>()
        verify { secondDownloadListener(capture(secondListenerNotifications)) }
        assertEquals(listOf(download1, download2), secondListenerNotifications)
    }

    @Test
    fun downloader_status_updates_are_delivered_on_connection() {
        // GIVEN
        //      not bound
        //      has status listeners
        val mockStatus: Downloader.Status = mockk()
        every { binder.status } returns mockStatus
        connection.registerStatusListener(firstStatusListener)
        connection.registerStatusListener(secondStatusListener)

        // WHEN
        //      service is bound
        connection.onServiceConnected(componentName, binder)

        // THEN
        //      downloader status is delivered
        verify { firstStatusListener(mockStatus) }
        verify { secondStatusListener(mockStatus) }
    }

    @Test
    fun downloader_status_not_requested_if_no_listeners() {
        // GIVEN
        //      not bound
        //      no status listeners

        // WHEN
        //      service is bound
        connection.onServiceConnected(componentName, binder)

        // THEN
        //      downloader status is not requested
        verify(exactly = 0) { binder.status }
    }

    @Test
    fun not_running_if_not_connected() {
        // GIVEN
        //      downloader is running
        //      connection not bound
        every { binder.isRunning } returns true

        // THEN
        //      not running
        assertFalse(connection.isRunning)
    }

    @Test
    fun is_running_from_binder_if_connected() {
        // GIVEN
        //      service bound
        every { binder.isRunning } returns true
        connection.onServiceConnected(componentName, binder)

        // WHEN
        //      is runnign flag accessed
        val isRunning = connection.isRunning

        // THEN
        //      call delegated to binder
        assertTrue(isRunning)
        verify(exactly = 1) { binder.isRunning }
    }

    @Test
    fun missed_updates_not_tracked_before_listeners_registered() {
        // GIVEN
        //      not bound
        //      some downloads requested without listener
        val request = Request(user, file)
        connection.download(request)
        val download = Download(request.uuid, DownloadState.RUNNING, 50, request.file, request)
        connection.registerDownloadListener(firstDownloadListener)
        every { binder.getDownload(request.uuid) } returns download

        // WHEN
        //      service is bound
        connection.onServiceConnected(componentName, binder)

        // THEN
        //      missed updates not redelivered
        verify(exactly = 0) { firstDownloadListener(any()) }
    }
}
