/* 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 org.mozilla.fenix.search.toolbar

import android.content.Context
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.graphics.drawable.toBitmap
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockk
import io.mockk.slot
import io.mockk.spyk
import io.mockk.verify
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.browser.toolbar.edit.EditToolbar
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.toolbar.Toolbar
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.search.SearchEngineSource
import org.mozilla.fenix.search.SearchFragmentState

@RunWith(FenixRobolectricTestRunner::class)
class ToolbarViewTest {
    @MockK(relaxed = true) private lateinit var interactor: ToolbarInteractor
    @MockK private lateinit var engine: Engine
    private lateinit var context: Context
    private lateinit var toolbar: BrowserToolbar
    private val defaultState: SearchFragmentState = SearchFragmentState(
        tabId = null,
        url = "",
        searchTerms = "",
        query = "",
        searchEngineSource = SearchEngineSource.Default(mockk {
            every { name } returns "Search Engine"
            every { icon } returns testContext.getDrawable(R.drawable.ic_search)!!.toBitmap()
        }),
        defaultEngineSource = mockk(relaxed = true),
        showSearchSuggestionsHint = false,
        showSearchSuggestions = false,
        showSearchShortcuts = false,
        areShortcutsAvailable = true,
        showClipboardSuggestions = false,
        showHistorySuggestions = false,
        showBookmarkSuggestions = false,
        searchAccessPoint = Event.PerformedSearch.SearchAccessPoint.NONE
    )

    @Before
    fun setup() {
        MockKAnnotations.init(this)
        context = ContextThemeWrapper(testContext, R.style.NormalTheme)
        toolbar = spyk(BrowserToolbar(context))
    }

    @Test
    fun `sets up interactor listeners`() {
        val urlCommitListener = slot<(String) -> Boolean>()
        val editListener = slot<Toolbar.OnEditListener>()
        every { toolbar.setOnUrlCommitListener(capture(urlCommitListener)) } just Runs
        every { toolbar.setOnEditListener(capture(editListener)) } just Runs

        buildToolbarView(isPrivate = false)

        assertFalse(urlCommitListener.captured("test"))
        verify { interactor.onUrlCommitted("test") }

        assertFalse(editListener.captured.onCancelEditing())
        verify { interactor.onEditingCanceled() }

        editListener.captured.onTextChanged("https://example.com")
        verify { interactor.onTextChanged("https://example.com") }
    }

    @Test
    fun `sets toolbar to normal mode`() {
        buildToolbarView(isPrivate = false)
        assertFalse(toolbar.private)
    }

    @Test
    fun `sets toolbar to private mode`() {
        buildToolbarView(isPrivate = true)
        assertTrue(toolbar.private)
    }

    @Test
    fun `View gets initialized only once`() {
        val view = buildToolbarView(false)
        assertFalse(view.isInitialized)

        view.update(defaultState)
        view.update(defaultState)
        view.update(defaultState)

        verify(exactly = 1) { toolbar.url = any() }
        verify(exactly = 1) { toolbar.setSearchTerms(any()) }
        verify(exactly = 1) { interactor.onTextChanged(any()) }
        // editMode gets called when the view is initialized. So it is called twice in this test
        verify(exactly = 2) { toolbar.editMode() }

        assertTrue(view.isInitialized)
    }

    @Test
    fun `URL gets set to the states query`() {
        val toolbarView = buildToolbarView(false)
        toolbarView.update(defaultState.copy(query = "Query"))

        assertEquals("Query", toolbarView.view.url)
    }

    @Test
    fun `URL gets set to the states pastedText if exists`() {
        val toolbarView = buildToolbarView(false)
        toolbarView.update(defaultState.copy(query = "Query", pastedText = "Pasted"))

        assertEquals("Pasted", toolbarView.view.url)
    }

    @Test
    fun `searchTerms get set if pastedText is null or empty`() {
        val toolbarView = buildToolbarView(false)
        toolbarView.update(defaultState.copy(query = "Query", pastedText = "", searchTerms = "Search Terms"))

        verify { toolbar.setSearchTerms("Search Terms") }
    }

    @Test
    fun `searchTerms don't get set if pastedText has a value`() {
        val toolbarView = buildToolbarView(false)
        toolbarView.update(
            defaultState.copy(query = "Query", pastedText = "PastedText", searchTerms = "Search Terms")
        )

        verify(exactly = 0) { toolbar.setSearchTerms("Search Terms") }
    }

    @Test
    fun `searchEngine name and icon get set on update`() {
        val editToolbar: EditToolbar = mockk(relaxed = true)
        every { toolbar.edit } returns editToolbar

        val toolbarView = buildToolbarView(false)
        toolbarView.update(defaultState)

        verify { editToolbar.setIcon(any(), "Search Engine") }
    }

    private fun buildToolbarView(isPrivate: Boolean) = ToolbarView(
        context,
        interactor,
        historyStorage = null,
        isPrivate = isPrivate,
        view = toolbar,
        engine = engine
    )
}
