/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.support.utils

import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageInfo
import android.content.pm.ResolveInfo
import android.net.Uri
import androidx.test.core.app.ApplicationProvider
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf

@RunWith(RobolectricTestRunner::class)
class BrowsersTest {
    private val context: Context
        get() = ApplicationProvider.getApplicationContext()

    @Test
    fun `with empty package manager`() {
        val browsers = Browsers.all(context)

        assertNull(browsers.defaultBrowser)
        assertNull(browsers.firefoxBrandedBrowser)
        assertFalse(browsers.hasFirefoxBrandedBrowserInstalled)
        assertTrue(browsers.installedBrowsers.isEmpty())
        assertFalse(browsers.hasThirdPartyDefaultBrowser)
        assertFalse(browsers.hasMultipleThirdPartyBrowsers)
        assertFalse(browsers.isDefaultBrowser)
        assertFalse(browsers.isFirefoxDefaultBrowser)
    }

    @Test
    fun `with firefox as default browser`() {
        pretendBrowsersAreInstalled(
            defaultBrowser = Browsers.KnownBrowser.FIREFOX.packageName
        )

        val browsers = Browsers.all(context)

        assertNotNull(browsers.defaultBrowser)
        assertEquals(Browsers.KnownBrowser.FIREFOX.packageName, browsers.defaultBrowser!!.packageName)

        assertNotNull(browsers.firefoxBrandedBrowser)
        assertEquals(Browsers.KnownBrowser.FIREFOX.packageName, browsers.firefoxBrandedBrowser!!.packageName)

        assertTrue(browsers.hasFirefoxBrandedBrowserInstalled)

        assertEquals(1, browsers.installedBrowsers.size)

        assertFalse(browsers.hasThirdPartyDefaultBrowser)
        assertFalse(browsers.hasMultipleThirdPartyBrowsers)
        assertFalse(browsers.isDefaultBrowser)
        assertTrue(browsers.isFirefoxDefaultBrowser)
    }

    @Test
    fun `with multiple browsers installed`() {
        pretendBrowsersAreInstalled(
            browsers = listOf(
                Browsers.KnownBrowser.FIREFOX_NIGHTLY.packageName,
                Browsers.KnownBrowser.FIREFOX.packageName,
                Browsers.KnownBrowser.CHROME.packageName,
                Browsers.KnownBrowser.SAMSUNG_INTERNET.packageName,
                Browsers.KnownBrowser.DUCKDUCKGO.packageName,
                Browsers.KnownBrowser.REFERENCE_BROWSER.packageName
            ),
            defaultBrowser = Browsers.KnownBrowser.REFERENCE_BROWSER.packageName)

        val browsers = Browsers.all(context)

        assertNotNull(browsers.defaultBrowser)
        assertEquals(Browsers.KnownBrowser.REFERENCE_BROWSER.packageName, browsers.defaultBrowser!!.packageName)

        assertNotNull(browsers.firefoxBrandedBrowser)
        assertEquals(Browsers.KnownBrowser.FIREFOX.packageName, browsers.firefoxBrandedBrowser!!.packageName)

        assertTrue(browsers.hasFirefoxBrandedBrowserInstalled)

        assertEquals(6, browsers.installedBrowsers.size)

        assertTrue(browsers.hasThirdPartyDefaultBrowser)
        assertTrue(browsers.hasMultipleThirdPartyBrowsers)
        assertFalse(browsers.isDefaultBrowser)
        assertFalse(browsers.isFirefoxDefaultBrowser)

        assertTrue(browsers.isInstalled(Browsers.KnownBrowser.REFERENCE_BROWSER))
        assertTrue(browsers.isInstalled(Browsers.KnownBrowser.FIREFOX_NIGHTLY))
        assertTrue(browsers.isInstalled(Browsers.KnownBrowser.FIREFOX))
        assertTrue(browsers.isInstalled(Browsers.KnownBrowser.CHROME))
        assertTrue(browsers.isInstalled(Browsers.KnownBrowser.REFERENCE_BROWSER))
        assertTrue(browsers.isInstalled(Browsers.KnownBrowser.DUCKDUCKGO))

        assertFalse(browsers.isInstalled(Browsers.KnownBrowser.CHROME_BETA))
        assertFalse(browsers.isInstalled(Browsers.KnownBrowser.FIREFOX_BETA))
        assertFalse(browsers.isInstalled(Browsers.KnownBrowser.ANDROID_STOCK_BROWSER))
        assertFalse(browsers.isInstalled(Browsers.KnownBrowser.UC_BROWSER))
    }

    @Test
    fun `With only firefox beta installed`() {
        pretendBrowsersAreInstalled(
            browsers = listOf(Browsers.KnownBrowser.FIREFOX_BETA.packageName),
            defaultBrowser = Browsers.KnownBrowser.FIREFOX_BETA.packageName)

        val browsers = Browsers.all(context)

        assertEquals(Browsers.KnownBrowser.FIREFOX_BETA.packageName, browsers.defaultBrowser!!.packageName)
        assertEquals(Browsers.KnownBrowser.FIREFOX_BETA.packageName, browsers.firefoxBrandedBrowser!!.packageName)
        assertTrue(browsers.hasFirefoxBrandedBrowserInstalled)
        assertTrue(browsers.isFirefoxDefaultBrowser)
    }

    @Test
    fun `With only firefox nightly installed`() {
        pretendBrowsersAreInstalled(
            browsers = listOf(Browsers.KnownBrowser.FIREFOX_NIGHTLY.packageName),
            defaultBrowser = Browsers.KnownBrowser.FIREFOX_NIGHTLY.packageName)

        val browsers = Browsers.all(context)

        assertEquals(Browsers.KnownBrowser.FIREFOX_NIGHTLY.packageName, browsers.defaultBrowser!!.packageName)
        assertEquals(Browsers.KnownBrowser.FIREFOX_NIGHTLY.packageName, browsers.firefoxBrandedBrowser!!.packageName)
        assertTrue(browsers.hasFirefoxBrandedBrowserInstalled)
        assertTrue(browsers.isFirefoxDefaultBrowser)
    }

    @Test
    fun `With only firefox aurora installed`() {
        pretendBrowsersAreInstalled(
            browsers = listOf(Browsers.KnownBrowser.FIREFOX_AURORA.packageName),
            defaultBrowser = Browsers.KnownBrowser.FIREFOX_AURORA.packageName)

        val browsers = Browsers.all(context)

        assertEquals(Browsers.KnownBrowser.FIREFOX_AURORA.packageName, browsers.defaultBrowser!!.packageName)
        assertEquals(Browsers.KnownBrowser.FIREFOX_AURORA.packageName, browsers.firefoxBrandedBrowser!!.packageName)
        assertTrue(browsers.hasFirefoxBrandedBrowserInstalled)
        assertTrue(browsers.isFirefoxDefaultBrowser)
    }

    @Test
    fun `With only firefox froid installed`() {
        pretendBrowsersAreInstalled(
            browsers = listOf(Browsers.KnownBrowser.FIREFOX_FDROID.packageName),
            defaultBrowser = Browsers.KnownBrowser.FIREFOX_FDROID.packageName)

        val browsers = Browsers.all(context)

        assertEquals(Browsers.KnownBrowser.FIREFOX_FDROID.packageName, browsers.defaultBrowser!!.packageName)
        assertEquals(Browsers.KnownBrowser.FIREFOX_FDROID.packageName, browsers.firefoxBrandedBrowser!!.packageName)
        assertTrue(browsers.hasFirefoxBrandedBrowserInstalled)
        assertTrue(browsers.isFirefoxDefaultBrowser)
    }

    @Test
    fun `With this app being the default browser`() {
        pretendBrowsersAreInstalled(
            browsers = listOf(context.packageName),
            defaultBrowser = context.packageName)

        val browsers = Browsers.all(context)

        assertTrue(browsers.isDefaultBrowser)
        assertFalse(browsers.isFirefoxDefaultBrowser)
        assertFalse(browsers.hasThirdPartyDefaultBrowser)
    }

    @Test
    fun `With unknown browsers`() {
        pretendBrowsersAreInstalled(
            browsers = listOf(
                "org.example.random",
                "org.example.a.browser",
                Browsers.KnownBrowser.REFERENCE_BROWSER.packageName
            ),
            defaultBrowser = "org.example.unknown.browser"
        )

        val browsers = Browsers.all(context)

        assertEquals("org.example.unknown.browser", browsers.defaultBrowser!!.packageName)
        assertNull(browsers.firefoxBrandedBrowser)
        assertFalse(browsers.hasFirefoxBrandedBrowserInstalled)
        assertEquals(2, browsers.installedBrowsers.size)
        assertTrue(browsers.hasThirdPartyDefaultBrowser)
        assertTrue(browsers.hasMultipleThirdPartyBrowsers)
        assertFalse(browsers.isDefaultBrowser)
        assertFalse(browsers.isFirefoxDefaultBrowser)

        val installedBrowsers = browsers.installedBrowsers.map { it.packageName }
        assertTrue(installedBrowsers.contains("org.example.unknown.browser"))
        assertTrue(installedBrowsers.contains(Browsers.KnownBrowser.REFERENCE_BROWSER.packageName))
    }

    @Test
    fun `With default browser that is not exported`() {
        pretendBrowsersAreInstalled(browsers = listOf(
            Browsers.KnownBrowser.FIREFOX_NIGHTLY.packageName,
            Browsers.KnownBrowser.FIREFOX.packageName))

        pretendBrowsersAreInstalled(
            defaultBrowser = "org.example.unknown.browser",
            defaultBrowserExported = false)

        val browsers = Browsers.all(context)

        assertNull(browsers.defaultBrowser)
        assertEquals(Browsers.KnownBrowser.FIREFOX.packageName, browsers.firefoxBrandedBrowser!!.packageName)
        assertTrue(browsers.hasFirefoxBrandedBrowserInstalled)
        browsers.installedBrowsers.forEach { println(it.packageName + " : " + it.exported) }
        assertEquals(2, browsers.installedBrowsers.size)
        assertFalse(browsers.hasThirdPartyDefaultBrowser)

        val installedBrowsers = browsers.installedBrowsers.map { it.packageName }
        assertTrue(installedBrowsers.contains(Browsers.KnownBrowser.FIREFOX.packageName))
        assertTrue(installedBrowsers.contains(Browsers.KnownBrowser.FIREFOX_NIGHTLY.packageName))
    }

    @Test
    fun `With some browsers not exported`() {
        pretendBrowsersAreInstalled(
            browsers = listOf(
                Browsers.KnownBrowser.FIREFOX.packageName))

        pretendBrowsersAreInstalled(
            browsers = listOf(
                "org.example.area51.browser",
                Browsers.KnownBrowser.CHROME.packageName),
            browsersExported = false)

        val browsers = Browsers.all(context)

        assertNull(browsers.defaultBrowser)
        assertEquals(Browsers.KnownBrowser.FIREFOX.packageName, browsers.firefoxBrandedBrowser!!.packageName)
        assertTrue(browsers.hasFirefoxBrandedBrowserInstalled)
        browsers.installedBrowsers.forEach { println(it.packageName + " : " + it.exported) }
        assertFalse(browsers.hasThirdPartyDefaultBrowser)
    }

    private fun pretendBrowsersAreInstalled(
        browsers: List<String> = listOf(),
        defaultBrowser: String? = null,
        url: String = "http://www.mozilla.org",
        browsersExported: Boolean = true,
        defaultBrowserExported: Boolean = true
    ) {
        val packageManager = context.packageManager
        val shadow = shadowOf(packageManager)

        browsers.forEach { packageName ->
            val intent = Intent(Intent.ACTION_VIEW)
            intent.`package` = packageName
            intent.data = Uri.parse(url)

            val packageInfo = PackageInfo()
            packageInfo.packageName = packageName

            shadow.addPackage(packageInfo)

            val activityInfo = ActivityInfo()
            activityInfo.exported = browsersExported
            activityInfo.packageName = packageName

            val resolveInfo = ResolveInfo()
            resolveInfo.resolvePackageName = packageName
            resolveInfo.activityInfo = activityInfo

            shadow.addResolveInfoForIntent(intent, resolveInfo)
        }

        if (defaultBrowser != null) {
            val intent = Intent(Intent.ACTION_VIEW)
            intent.data = Uri.parse(url)

            val activityInfo = ActivityInfo()
            activityInfo.exported = defaultBrowserExported
            activityInfo.packageName = defaultBrowser

            val resolveInfo = ResolveInfo()
            resolveInfo.resolvePackageName = defaultBrowser
            resolveInfo.activityInfo = activityInfo

            shadow.addResolveInfoForIntent(intent, resolveInfo)
        }
    }
}