/* 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/. */

@file:Suppress("TooManyFunctions", "TooGenericExceptionCaught")

package org.mozilla.fenix.ui.robots

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.SystemClock
import android.util.Log
import android.widget.TimePicker
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.PickerActions
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.BundleMatchers
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.By.text
import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiObjectNotFoundException
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.mediasession.MediaSession
import org.hamcrest.CoreMatchers.allOf
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
import org.mozilla.fenix.helpers.Constants.RETRY_COUNT
import org.mozilla.fenix.helpers.SessionLoadedIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.waitForObjects
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
import java.time.LocalDate

class BrowserRobot {
    private lateinit var sessionLoadedIdlingResource: SessionLoadedIdlingResource

    fun waitForPageToLoad() = progressBar.waitUntilGone(waitingTime)

    fun verifyCurrentPrivateSession(context: Context) {
        val selectedTab = context.components.core.store.state.selectedTab
        assertTrue("Current session is private", selectedTab?.content?.private ?: false)
    }

    fun verifyUrl(url: String) {
        sessionLoadedIdlingResource = SessionLoadedIdlingResource()

        runWithIdleRes(sessionLoadedIdlingResource) {
            assertTrue(
                mDevice.findObject(
                    UiSelector()
                        .resourceId("$packageName:id/mozac_browser_toolbar_url_view")
                        .textContains(url.replace("http://", "")),
                ).waitForExists(waitingTime),
            )
        }
    }

    fun verifyHelpUrl() {
        verifyUrl("support.mozilla.org/")
    }

    fun verifyWhatsNewURL() {
        verifyUrl("support.mozilla.org/")
    }

    fun verifyRateOnGooglePlayURL() {
        verifyUrl("play.google.com/store/apps/details?id=org.mozilla.fenix")
    }

    /* Asserts that the text within DOM element with ID="testContent" has the given text, i.e.
    *  document.querySelector('#testContent').innerText == expectedText
    *
    */

    fun verifyPageContent(expectedText: String) {
        sessionLoadedIdlingResource = SessionLoadedIdlingResource()

        mDevice.waitNotNull(
            Until.findObject(By.res("$packageName:id/engineView")),
            waitingTime,
        )

        runWithIdleRes(sessionLoadedIdlingResource) {
            assertTrue(
                "Page didn't load or doesn't contain the expected text",
                mDevice.findObject(UiSelector().textContains(expectedText)).waitForExists(waitingTime),
            )
        }
    }

    /* Verifies the information displayed on the about:cache page */
    fun verifyNetworkCacheIsEmpty(storage: String) {
        val memorySection = mDevice.findObject(UiSelector().description(storage))

        val gridView =
            if (storage == "memory") {
                memorySection.getFromParent(
                    UiSelector()
                        .className("android.widget.GridView")
                        .index(2),
                )
            } else {
                memorySection.getFromParent(
                    UiSelector()
                        .className("android.widget.GridView")
                        .index(4),
                )
            }

        val cacheSizeInfo =
            gridView.getChild(
                UiSelector().text("Number of entries:"),
            ).getFromParent(
                UiSelector().text("0"),
            )

        for (i in 1..RETRY_COUNT) {
            try {
                assertTrue(cacheSizeInfo.waitForExists(waitingTime))
                break
            } catch (e: AssertionError) {
                browserScreen {
                }.openThreeDotMenu {
                }.refreshPage { }
            }
        }
    }

    fun verifyTabCounter(expectedText: String) {
        val counter =
            mDevice.findObject(
                UiSelector()
                    .resourceId("$packageName:id/counter_text")
                    .text(expectedText),
            )
        assertTrue(counter.waitForExists(waitingTime))
    }

    fun verifySnackBarText(expectedText: String) {
        mDevice.waitForObjects(mDevice.findObject(UiSelector().textContains(expectedText)))

        assertTrue(
            mDevice.findObject(
                UiSelector()
                    .textContains(expectedText),
            ).waitForExists(waitingTime),
        )
    }

    fun verifyLinkContextMenuItems(containsURL: Uri) {
        mDevice.waitNotNull(
            Until.findObject(By.textContains(containsURL.toString())),
            waitingTime,
        )
        mDevice.waitNotNull(
            Until.findObject(text("Open link in new tab")),
            waitingTime,
        )
        mDevice.waitNotNull(
            Until.findObject(text("Open link in private tab")),
            waitingTime,
        )
        mDevice.waitNotNull(Until.findObject(text("Copy link")), waitingTime)
        mDevice.waitNotNull(Until.findObject(text("Share link")), waitingTime)
    }

    fun verifyLinkImageContextMenuItems(containsURL: Uri) {
        mDevice.waitNotNull(Until.findObject(By.textContains(containsURL.toString())))
        mDevice.waitNotNull(
            Until.findObject(text("Open link in new tab")),
            waitingTime,
        )
        mDevice.waitNotNull(
            Until.findObject(text("Open link in private tab")),
            waitingTime,
        )
        mDevice.waitNotNull(Until.findObject(text("Copy link")), waitingTime)
        mDevice.waitNotNull(Until.findObject(text("Share link")), waitingTime)
        mDevice.waitNotNull(
            Until.findObject(text("Open image in new tab")),
            waitingTime,
        )
        mDevice.waitNotNull(Until.findObject(text("Save image")), waitingTime)
        mDevice.waitNotNull(
            Until.findObject(text("Copy image location")),
            waitingTime,
        )
    }

    fun verifyNavURLBarHidden() = assertNavURLBarHidden()

    fun verifySecureConnectionLockIcon() = assertSecureConnectionLockIcon()

    fun verifyMenuButton() = assertMenuButton()

    fun verifyNavURLBarItems() {
        navURLBar().waitForExists(waitingTime)
        verifyMenuButton()
        verifyTabCounter("1")
        verifySearchBar()
        verifySecureConnectionLockIcon()
        verifyHomeScreenButton()
    }

    fun verifyNoLinkImageContextMenuItems(containsURL: Uri) {
        mDevice.waitNotNull(Until.findObject(By.textContains(containsURL.toString())))
        mDevice.waitNotNull(
            Until.findObject(text("Open image in new tab")),
            waitingTime,
        )
        mDevice.waitNotNull(Until.findObject(text("Save image")), waitingTime)
        mDevice.waitNotNull(
            Until.findObject(text("Copy image location")),
            waitingTime,
        )
    }

    fun verifyNotificationDotOnMainMenu() {
        assertTrue(
            mDevice.findObject(UiSelector().resourceId("$packageName:id/notification_dot"))
                .waitForExists(waitingTime),
        )
    }

    fun verifyHomeScreenButton() = assertHomeScreenButton()

    fun verifySearchBar() = assertSearchBar()

    fun dismissContentContextMenu(containsURL: Uri) {
        onView(withText(containsURL.toString()))
            .inRoot(isDialog())
            .check(matches(isDisplayed()))
            .perform(ViewActions.pressBack())
    }

    fun clickContextOpenLinkInNewTab() {
        mDevice.waitNotNull(
            Until.findObject(text("Open link in new tab")),
            waitingTime,
        )

        val menuOpenInNewTab = mDevice.findObject(text("Open link in new tab"))
        menuOpenInNewTab.click()
    }

    fun clickContextOpenLinkInPrivateTab() {
        mDevice.waitNotNull(
            Until.findObject(text("Open link in private tab")),
            waitingTime,
        )

        val menuOpenInPrivateTab = mDevice.findObject(text("Open link in private tab"))
        menuOpenInPrivateTab.click()
    }

    fun clickContextCopyLink() {
        mDevice.waitNotNull(Until.findObject(text("Copy link")), waitingTime)

        val menuCopyLink = mDevice.findObject(text("Copy link"))
        menuCopyLink.click()
    }

    fun clickContextShareLink(url: Uri) {
        mDevice.waitNotNull(Until.findObject(text("Share link")), waitingTime)

        val menuShareLink = mDevice.findObject(text("Share link"))
        menuShareLink.click()

        // verify share intent is launched and matched with associated passed in URL
        Intents.intended(
            allOf(
                IntentMatchers.hasAction(Intent.ACTION_CHOOSER),
                IntentMatchers.hasExtras(
                    allOf(
                        BundleMatchers.hasEntry(
                            Intent.EXTRA_INTENT,
                            allOf(
                                IntentMatchers.hasAction(Intent.ACTION_SEND),
                                IntentMatchers.hasType("text/plain"),
                                IntentMatchers.hasExtra(
                                    Intent.EXTRA_TEXT,
                                    url.toString(),
                                ),
                            ),
                        ),
                    ),
                ),
            ),
        )
    }

    fun clickContextCopyImageLocation() {
        mDevice.waitNotNull(
            Until.findObject(text("Copy image location")),
            waitingTime,
        )

        val menuCopyImageLocation = mDevice.findObject(text("Copy image location"))
        menuCopyImageLocation.click()
    }

    fun clickContextOpenImageNewTab() {
        mDevice.waitNotNull(
            Until.findObject(text("Open image in new tab")),
            waitingTime,
        )

        val menuOpenImageNewTab = mDevice.findObject(text("Open image in new tab"))
        menuOpenImageNewTab.click()
    }

    fun clickContextSaveImage() {
        mDevice.waitNotNull(Until.findObject(text("Save image")), waitingTime)

        val menuSaveImage = mDevice.findObject(text("Save image"))
        menuSaveImage.click()
    }

    fun createBookmark(url: Uri, folder: String? = null) {
        navigationToolbar {
        }.enterURLAndEnterToBrowser(url) {
            // needs to wait for the right url to load before saving a bookmark
            verifyUrl(url.toString())
        }.openThreeDotMenu {
        }.bookmarkPage {
        }.takeIf { !folder.isNullOrBlank() }?.let {
            it.openThreeDotMenu {
            }.editBookmarkPage {
                setParentFolder(folder!!)
                saveEditBookmark()
            }
        }
    }

    fun clickLinkMatchingText(expectedText: String) =
        clickPageObject(webPageItemContainingText(expectedText))

    fun longClickLink(expectedText: String) =
        longClickPageObject(webPageItemWithText(expectedText))

    fun longClickMatchingText(expectedText: String) =
        longClickPageObject(webPageItemContainingText(expectedText))

    fun longClickAndCopyText(expectedText: String, selectAll: Boolean = false) {
        longClickPageObject(webPageItemContainingText(expectedText))

        // Click Select all from the text selection toolbar
        if (selectAll) {
            mDevice.findObject(UiSelector().textContains("Select all")).waitForExists(waitingTime)
            val selectAllText = mDevice.findObject(By.textContains("Select all"))
            selectAllText.click()
        }

        // Click Copy from the text selection toolbar
        mDevice.findObject(UiSelector().textContains("Copy")).waitForExists(waitingTime)
        val copyText = mDevice.findObject(By.textContains("Copy"))
        copyText.click()
    }

    fun longClickAndSearchText(searchButton: String, expectedText: String) {
        longClickPageObject(webPageItemContainingText(expectedText))

        // Click search from the text selection toolbar
        mDevice.findObject(UiSelector().textContains(searchButton)).waitForExists(waitingTime)
        val searchText = mDevice.findObject(By.textContains(searchButton))
        searchText.click()
    }

    fun snackBarButtonClick() {
        val switchButton =
            mDevice.findObject(
                UiSelector()
                    .resourceId("$packageName:id/snackbar_btn"),
            )
        switchButton.waitForExists(waitingTime)
        switchButton.clickAndWaitForNewWindow(waitingTime)
    }

    fun verifySaveLoginPromptIsShown() = clickPageObject(webPageItemWithResourceId("submit"))

    fun verifyUpdateLoginPromptIsShown() {
        clickPageObject(webPageItemWithResourceId("submit"))
        mDevice.waitNotNull(Until.findObjects(text("Update")))
    }

    fun saveLoginFromPrompt(optionToSaveLogin: String) {
        mDevice.waitForObjects(
            mDevice.findObject(
                UiSelector().resourceId("$packageName:id/feature_prompt_login_fragment"),
            ),
        )
        mDevice.findObject(text(optionToSaveLogin)).click()
    }

    fun enterPassword(password: String) {
        clickPageObject(webPageItemWithResourceId("password"))
        setPageObjectText(webPageItemWithResourceId("password"), password)

        assertTrue(mDevice.findObject(UiSelector().text(password)).waitUntilGone(waitingTime))
    }

    fun clickMediaPlayerPlayButton() = clickPageObject(webPageItemWithText("Play"))

    /**
     * Get the current playback state of the currently selected tab.
     * The result may be null if there if the currently playing media tab cannot be found in [store]
     *
     * @param store [BrowserStore] from which to get data about the current tab's state.
     * @return nullable [MediaSession.PlaybackState] indicating the media playback state for the current tab.
     */
    private fun getCurrentPlaybackState(store: BrowserStore): MediaSession.PlaybackState? {
        return store.state.selectedTab?.mediaSessionState?.playbackState
    }

    /**
     * Asserts that in [waitingTime] the playback state of the current tab will be [expectedState].
     *
     * @param store [BrowserStore] from which to get data about the current tab's state.
     * @param expectedState [MediaSession.PlaybackState] the playback state that will be asserted
     * @param waitingTime maximum time the test will wait for the playback state to become [expectedState]
     * before failing the assertion.
     */
    fun assertPlaybackState(store: BrowserStore, expectedState: MediaSession.PlaybackState) {
        val startMills = SystemClock.uptimeMillis()
        var currentMills: Long = 0
        while (currentMills <= waitingTime) {
            if (expectedState == getCurrentPlaybackState(store)) return
            currentMills = SystemClock.uptimeMillis() - startMills
        }
        fail("Playback did not moved to state: $expectedState")
    }

    fun swipeNavBarRight(tabUrl: String) {
        // failing to swipe on Firebase sometimes, so it tries again
        try {
            navURLBar().swipeRight(2)
            assertTrue(mDevice.findObject(UiSelector().text(tabUrl)).waitUntilGone(waitingTime))
        } catch (e: AssertionError) {
            navURLBar().swipeRight(2)
            assertTrue(mDevice.findObject(UiSelector().text(tabUrl)).waitUntilGone(waitingTime))
        }
    }

    fun swipeNavBarLeft(tabUrl: String) {
        // failing to swipe on Firebase sometimes, so it tries again
        try {
            navURLBar().swipeLeft(2)
            assertTrue(mDevice.findObject(UiSelector().text(tabUrl)).waitUntilGone(waitingTime))
        } catch (e: AssertionError) {
            navURLBar().swipeLeft(2)
            assertTrue(mDevice.findObject(UiSelector().text(tabUrl)).waitUntilGone(waitingTime))
        }
    }

    fun clickTabCrashedRestoreButton() {
        assertTrue(
            mDevice.findObject(UiSelector().resourceId("$packageName:id/restoreTabButton"))
                .waitForExists(waitingTime),
        )

        val tabCrashRestoreButton = mDevice.findObject(UiSelector().resourceIdMatches("$packageName:id/restoreTabButton"))
        tabCrashRestoreButton.click()
    }

    fun fillAndSubmitLoginCredentials(userName: String, password: String) {
        mDevice.waitForIdle(waitingTime)
        setPageObjectText(webPageItemWithResourceId("username"), userName)
        setPageObjectText(webPageItemWithResourceId("password"), password)
        clickPageObject(webPageItemWithResourceId("submit"))

        mDevice.waitForObjects(mDevice.findObject(UiSelector().resourceId("$packageName:id/save_confirm")))
    }

    fun clearUserNameLoginCredential() {
        mDevice.waitForObjects(webPageItemWithResourceId("username"))
        webPageItemWithResourceId("username").clearTextField()
        mDevice.waitForIdle(waitingTime)
    }

    fun clickSuggestedLoginsButton() {
        var currentTries = 0
        while (currentTries++ < 3) {
            try {
                mDevice.waitForObjects(suggestedLogins)
                suggestedLogins.click()
                mDevice.waitForObjects(suggestedLogins)
                break
            } catch (e: UiObjectNotFoundException) {
                clickPageObject(webPageItemWithResourceId("username"))
            }
        }
    }

    fun clickStreetAddressTextBox() = clickPageObject(webPageItemWithResourceId("streetAddress"))

    fun clickSelectAddressButton() {
        selectAddressButton.waitForExists(waitingTime)
        selectAddressButton.clickAndWaitForNewWindow(waitingTime)
    }

    fun clickCardNumberTextBox() = clickPageObject(webPageItemWithResourceId("cardNumber"))

    fun clickSelectCreditCardButton() {
        selectCreditCardButton.waitForExists(waitingTime)
        selectCreditCardButton.clickAndWaitForNewWindow(waitingTime)
    }

    fun clickLoginSuggestion(userName: String) {
        val loginSuggestion =
            mDevice.findObject(
                UiSelector()
                    .textContains(userName)
                    .resourceId("$packageName:id/username"),
            )

        loginSuggestion.click()
    }

    fun clickAddressSuggestion(streetName: String) {
        addressSuggestion(streetName).waitForExists(waitingTime)
        addressSuggestion(streetName).click()
    }

    fun clickCreditCardSuggestion(creditCardNumber: String) {
        creditCardSuggestion(creditCardNumber).waitForExists(waitingTime)
        creditCardSuggestion(creditCardNumber).click()
    }

    fun verifySuggestedUserName(userName: String) {
        mDevice.findObject(
            UiSelector()
                .resourceId("$packageName:id/mozac_feature_login_multiselect_expand"),
        ).waitForExists(waitingTime)

        assertTrue(
            mDevice.findObject(UiSelector().textContains(userName)).waitForExists(waitingTime),
        )
    }

    fun verifyPrefilledLoginCredentials(userName: String) {
        var currentTries = 0
        // Sometimes the assertion of the pre-filled logins fails so we are re-trying after refreshing the page
        while (currentTries++ < 3) {
            try {
                mDevice.waitForObjects(webPageItemWithResourceId("username"))
                assertTrue(webPageItemWithResourceId("username").text.equals(userName))

                break
            } catch (e: AssertionError) {
                browserScreen {
                }.openThreeDotMenu {
                }.refreshPage {
                    clearUserNameLoginCredential()
                    clickSuggestedLoginsButton()
                    verifySuggestedUserName(userName)
                    clickLoginSuggestion(userName)
                }
            }
        }
        mDevice.waitForObjects(webPageItemWithResourceId("username"))
        assertTrue(webPageItemWithResourceId("username").text.equals(userName))
    }

    fun verifyAutofilledAddress(streetAddress: String) {
        mDevice.waitForObjects(webPageItemContainingTextAndResourceId("streetAddress", streetAddress))
        assertTrue(
            webPageItemContainingTextAndResourceId("streetAddress", streetAddress)
                .waitForExists(waitingTime),
        )
    }

    fun verifyAutofilledCreditCard(creditCardNumber: String) {
        mDevice.waitForObjects(webPageItemContainingTextAndResourceId("cardNumber", creditCardNumber))
        assertTrue(
            webPageItemContainingTextAndResourceId("cardNumber", creditCardNumber)
                .waitForExists(waitingTime),
        )
    }

    fun verifyPrefilledPWALoginCredentials(userName: String, shortcutTitle: String) {
        mDevice.waitForIdle(waitingTime)

        var currentTries = 0
        while (currentTries++ < 3) {
            try {
                assertTrue(webPageItemWithResourceId("submit").waitForExists(waitingTime))
                webPageItemWithResourceId("submit").click()
                assertTrue(webPageItemWithResourceId("username").text.equals(userName))
                break
            } catch (e: AssertionError) {
                addToHomeScreen {
                }.searchAndOpenHomeScreenShortcut(shortcutTitle) {}
            }
        }
    }

    fun verifySaveLoginPromptIsDisplayed() {
        assertTrue(
            mDevice.findObject(
                UiSelector()
                    .resourceId("$packageName:id/feature_prompt_login_fragment"),
            ).waitForExists(waitingTime),
        )
    }

    fun verifyTrackingProtectionWebContent(state: String) {
        for (i in 1..RETRY_COUNT) {
            try {
                assertTrue(
                    mDevice.findObject(
                        UiSelector().textContains(state),
                    ).waitForExists(waitingTimeLong),
                )

                break
            } catch (e: AssertionError) {
                if (i == RETRY_COUNT) {
                    throw e
                } else {
                    Log.e("TestLog", "On try $i, trackers are not: $state")

                    navigationToolbar {
                    }.openThreeDotMenu {
                    }.refreshPage {
                    }
                }
            }
        }
    }

    fun clickSetCookiesButton() = clickPageObject(webPageItemWithResourceId("setCookies"))

    fun verifyCookiesProtectionHint() {
        val hintMessage =
            mDevice.findObject(
                UiSelector()
                    .textContains(getStringResource(R.string.tcp_cfr_message)),
            )
        assertTrue(hintMessage.waitForExists(waitingTime))
    }

    fun clickForm(formType: String) {
        when (formType) {
            "Calendar Form" -> {
                clickPageObject(webPageItemWithResourceId("calendar"))
                mDevice.waitForIdle(waitingTime)
            }
            "Clock Form" -> {
                clickPageObject(webPageItemWithResourceId("clock"))
                mDevice.waitForIdle(waitingTime)
            }
            "Color Form" -> {
                clickPageObject(webPageItemWithResourceId("colorPicker"))
                mDevice.waitForIdle(waitingTime)
            }
            "Drop-down Form" -> {
                clickPageObject(webPageItemWithResourceId("dropDown"))
                mDevice.waitForIdle(waitingTime)
            }
        }
    }

    fun clickFormViewButton(button: String) = mDevice.findObject(UiSelector().textContains(button)).click()

    fun selectDate() {
        mDevice.findObject(UiSelector().resourceId("android:id/month_view")).waitForExists(waitingTime)

        mDevice.findObject(
            UiSelector()
                .textContains("$currentDay")
                .descriptionContains("$currentDay $currentMonth $currentYear"),
        ).click()
    }

    fun selectTime(hour: Int, minute: Int) =
        onView(
            isAssignableFrom(TimePicker::class.java),
        ).inRoot(
            isDialog(),
        ).perform(PickerActions.setTime(hour, minute))

    fun selectColor(hexValue: String) {
        mDevice.findObject(
            UiSelector()
                .textContains("Choose a color")
                .resourceId("$packageName:id/alertTitle"),
        ).waitForExists(waitingTime)

        val colorSelection =
            mDevice.findObject(
                UiSelector()
                    .resourceId("$packageName:id/color_item")
                    .descriptionContains(hexValue),
            )
        colorSelection.click()
    }

    fun selectDropDownOption(optionName: String) {
        mDevice.findObject(
            UiSelector().resourceId("$packageName:id/customPanel"),
        ).waitForExists(waitingTime)
        mDevice.findObject(UiSelector().textContains(optionName)).click()
    }

    fun clickSubmitDateButton() = clickPageObject(webPageItemWithResourceId("submitDate"))

    fun clickSubmitTimeButton() = clickPageObject(webPageItemWithResourceId("submitTime"))

    fun clickSubmitColorButton() = clickPageObject(webPageItemWithResourceId("submitColor"))

    fun clickSubmitDropDownButton() = clickPageObject(webPageItemWithResourceId("submitOption"))

    fun verifySelectedDate() {
        for (i in 1..RETRY_COUNT) {
            try {
                assertTrue(
                    mDevice.findObject(
                        UiSelector()
                            .text("Selected date is: $currentDate"),
                    ).waitForExists(waitingTime),
                )

                break
            } catch (e: AssertionError) {
                Log.e("TestLog", "Selected time isn't displayed ${e.localizedMessage}")

                clickForm("Calendar Form")
                selectDate()
                clickFormViewButton("OK")
                clickSubmitDateButton()
            }
        }

        assertTrue(
            mDevice.findObject(
                UiSelector()
                    .text("Selected date is: $currentDate"),
            ).waitForExists(waitingTime),
        )
    }

    fun verifyNoDateIsSelected() {
        assertFalse(
            mDevice.findObject(
                UiSelector()
                    .text("Selected date is: $currentDate"),
            ).waitForExists(waitingTime),
        )
    }

    fun verifySelectedTime(hour: Int, minute: Int) {
        for (i in 1..RETRY_COUNT) {
            try {
                assertTrue(
                    mDevice.findObject(
                        UiSelector()
                            .text("Selected time is: $hour:$minute"),
                    ).waitForExists(waitingTime),
                )

                break
            } catch (e: AssertionError) {
                Log.e("TestLog", "Selected time isn't displayed ${e.localizedMessage}")

                clickForm("Clock Form")
                clickFormViewButton("CLEAR")
                clickForm("Clock Form")
                selectTime(hour, minute)
                clickFormViewButton("OK")
                clickSubmitTimeButton()
            }
        }

        assertTrue(
            mDevice.findObject(
                UiSelector()
                    .text("Selected time is: $hour:$minute"),
            ).waitForExists(waitingTime),
        )
    }

    fun verifySelectedColor(hexValue: String) {
        for (i in 1..RETRY_COUNT) {
            try {
                assertTrue(
                    mDevice.findObject(
                        UiSelector()
                            .text("Selected color is: $hexValue"),
                    ).waitForExists(waitingTime),
                )

                break
            } catch (e: AssertionError) {
                Log.e("TestLog", "Selected color isn't displayed ${e.localizedMessage}")

                clickForm("Color Form")
                selectColor(hexValue)
                clickFormViewButton("SET")
                clickSubmitColorButton()
            }
        }

        assertTrue(
            mDevice.findObject(
                UiSelector()
                    .text("Selected color is: $hexValue"),
            ).waitForExists(waitingTime),
        )
    }

    fun verifySelectedDropDownOption(optionName: String) {
        for (i in 1..RETRY_COUNT) {
            try {
                mDevice.findObject(
                    UiSelector()
                        .textContains("Submit drop down option")
                        .resourceId("submitOption"),
                ).waitForExists(waitingTime)

                assertTrue(
                    mDevice.findObject(
                        UiSelector()
                            .text("Selected option is: $optionName"),
                    ).waitForExists(waitingTime),
                )

                break
            } catch (e: AssertionError) {
                Log.e("TestLog", "Selected option isn't displayed ${e.localizedMessage}")

                clickForm("Drop-down Form")
                selectDropDownOption(optionName)
                clickSubmitDropDownButton()
            }
        }

        assertTrue(
            mDevice.findObject(
                UiSelector()
                    .text("Selected option is: $optionName"),
            ).waitForExists(waitingTime),
        )
    }

    fun verifyNoTimeIsSelected(hour: Int, minute: Int) {
        assertFalse(
            mDevice.findObject(
                UiSelector()
                    .text("Selected date is: $hour:$minute"),
            ).waitForExists(waitingTime),
        )
    }

    fun verifyColorIsNotSelected(hexValue: String) {
        assertFalse(
            mDevice.findObject(
                UiSelector()
                    .text("Selected date is: $hexValue"),
            ).waitForExists(waitingTime),
        )
    }

    class Transition {
        private fun threeDotButton() = onView(
            allOf(
                ViewMatchers.withContentDescription(
                    "Menu",
                ),
            ),
        )

        fun openThreeDotMenu(interact: ThreeDotMenuMainRobot.() -> Unit): ThreeDotMenuMainRobot.Transition {
            mDevice.waitForIdle(waitingTime)
            threeDotButton().perform(ViewActions.click())

            ThreeDotMenuMainRobot().interact()
            return ThreeDotMenuMainRobot.Transition()
        }

        fun openNavigationToolbar(interact: NavigationToolbarRobot.() -> Unit): NavigationToolbarRobot.Transition {
            mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar"))
                .waitForExists(waitingTime)
            navURLBar().click()

            NavigationToolbarRobot().interact()
            return NavigationToolbarRobot.Transition()
        }

        fun openTabDrawer(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
            mDevice.waitForObjects(
                mDevice.findObject(
                    UiSelector()
                        .resourceId("$packageName:id/mozac_browser_toolbar_browser_actions"),
                ),
                waitingTime,
            )

            tabsCounter().click()

            mDevice.waitForObjects(
                mDevice.findObject(
                    UiSelector().resourceId("$packageName:id/new_tab_button"),
                ),
                waitingTime,
            )

            TabDrawerRobot().interact()
            return TabDrawerRobot.Transition()
        }

        fun openTabButtonShortcutsMenu(interact: NavigationToolbarRobot.() -> Unit): NavigationToolbarRobot.Transition {
            mDevice.waitNotNull(Until.findObject(By.desc("Tabs")))
            tabsCounter().click(LONG_CLICK_DURATION)

            NavigationToolbarRobot().interact()
            return NavigationToolbarRobot.Transition()
        }

        fun openNotificationShade(interact: NotificationRobot.() -> Unit): NotificationRobot.Transition {
            mDevice.openNotification()

            NotificationRobot().interact()
            return NotificationRobot.Transition()
        }

        fun goToHomescreen(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
            assertTrue(
                mDevice.findObject(UiSelector().description("Home screen"))
                    .waitForExists(waitingTime),
            )

            onView(withContentDescription("Home screen"))
                .check(matches(isDisplayed()))
                .click()

            assertTrue(
                mDevice.findObject(UiSelector().resourceId("$packageName:id/homeLayout"))
                    .waitForExists(waitingTime) ||
                    mDevice.findObject(
                        UiSelector().text(
                            getStringResource(R.string.onboarding_home_screen_jump_back_contextual_hint_2),
                        ),
                    ).waitForExists(waitingTime),
            )

            HomeScreenRobot().interact()
            return HomeScreenRobot.Transition()
        }

        fun goBack(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
            mDevice.pressBack()

            HomeScreenRobot().interact()
            return HomeScreenRobot.Transition()
        }

        fun clickTabCrashedCloseButton(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
            assertTrue(
                mDevice.findObject(UiSelector().resourceId("$packageName:id/closeTabButton"))
                    .waitForExists(waitingTime),
            )

            val tabCrashedCloseButton = mDevice.findObject(text("Close tab"))
            tabCrashedCloseButton.click()

            mDevice.waitForIdle()

            HomeScreenRobot().interact()
            return HomeScreenRobot.Transition()
        }

        fun clickShareSelectedText(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
            val shareTextButton = mDevice.findObject(By.textContains("Share"))
            shareTextButton.click()

            ShareOverlayRobot().interact()
            return ShareOverlayRobot.Transition()
        }

        fun clickDownloadLink(title: String, interact: DownloadRobot.() -> Unit): DownloadRobot.Transition {
            assertTrue(
                "$title download link not found",
                webPageItemContainingText(title).waitForExists(waitingTime),
            )

            clickPageObject(webPageItemContainingText(title))

            DownloadRobot().interact()
            return DownloadRobot.Transition()
        }

        fun clickStartCameraButton(interact: SitePermissionsRobot.() -> Unit): SitePermissionsRobot.Transition {
            // Test page used for testing permissions located at https://mozilla-mobile.github.io/testapp/permissions
            clickPageObject(webPageItemWithText("Open camera"))

            SitePermissionsRobot().interact()
            return SitePermissionsRobot.Transition()
        }

        fun clickStartMicrophoneButton(interact: SitePermissionsRobot.() -> Unit): SitePermissionsRobot.Transition {
            // Test page used for testing permissions located at https://mozilla-mobile.github.io/testapp/permissions
            clickPageObject(webPageItemWithText("Open microphone"))

            SitePermissionsRobot().interact()
            return SitePermissionsRobot.Transition()
        }

        fun clickStartAudioVideoButton(interact: SitePermissionsRobot.() -> Unit): SitePermissionsRobot.Transition {
            // Test page used for testing permissions located at https://mozilla-mobile.github.io/testapp/permissions
            clickPageObject(webPageItemWithText("Camera & Microphone"))

            SitePermissionsRobot().interact()
            return SitePermissionsRobot.Transition()
        }

        fun clickOpenNotificationButton(interact: SitePermissionsRobot.() -> Unit): SitePermissionsRobot.Transition {
            // Test page used for testing permissions located at https://mozilla-mobile.github.io/testapp/permissions
            clickPageObject(webPageItemWithText("Open notifications dialogue"))
            mDevice.waitForObjects(mDevice.findObject(UiSelector().textContains("Allow to send notifications?")))

            SitePermissionsRobot().interact()
            return SitePermissionsRobot.Transition()
        }

        fun clickGetLocationButton(interact: SitePermissionsRobot.() -> Unit): SitePermissionsRobot.Transition {
            // Test page used for testing permissions located at https://mozilla-mobile.github.io/testapp/permissions
            clickPageObject(webPageItemWithText("Get Location"))

            SitePermissionsRobot().interact()
            return SitePermissionsRobot.Transition()
        }

        fun clickRequestStorageAccessButton(interact: SitePermissionsRobot.() -> Unit): SitePermissionsRobot.Transition {
            clickPageObject(webPageItemContainingText("requestStorageAccess()"))

            SitePermissionsRobot().interact()
            return SitePermissionsRobot.Transition()
        }

        fun openSiteSecuritySheet(interact: SiteSecurityRobot.() -> Unit): SiteSecurityRobot.Transition {
            siteSecurityToolbarButton().waitForExists(waitingTime)
            siteSecurityToolbarButton().clickAndWaitForNewWindow(waitingTime)

            SiteSecurityRobot().interact()
            return SiteSecurityRobot.Transition()
        }
    }
}

fun browserScreen(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
    BrowserRobot().interact()
    return BrowserRobot.Transition()
}

fun navURLBar() = mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar"))

fun searchBar() = onView(withId(R.id.mozac_browser_toolbar_url_view))

fun homeScreenButton() = onView(withContentDescription(R.string.browser_toolbar_home))

private fun assertHomeScreenButton() =
    homeScreenButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))

private fun assertSearchBar() = searchBar().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))

private fun assertNavURLBar() = assertTrue(navURLBar().waitForExists(waitingTime))

private fun assertNavURLBarHidden() = assertTrue(navURLBar().waitUntilGone(waitingTime))

private fun assertSecureConnectionLockIcon() {
    onView(withId(R.id.mozac_browser_toolbar_security_indicator))
        .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}

private fun menuButton() = onView(withId(R.id.icon))

private fun assertMenuButton() {
    menuButton()
        .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}

private fun tabsCounter() =
    mDevice.findObject(By.res("$packageName:id/mozac_browser_toolbar_browser_actions"))

private var progressBar =
    mDevice.findObject(
        UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_progress"),
    )

private val suggestedLogins = mDevice.findObject(UiSelector().resourceId("$packageName:id/loginSelectBar"))
private val selectAddressButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/select_address_header"))
private val selectCreditCardButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/select_credit_card_header"))

private fun addressSuggestion(streetName: String) =
    mDevice.findObject(
        UiSelector()
            .resourceId("$packageName:id/address_name")
            .textContains(streetName),
    )

private fun creditCardSuggestion(creditCardNumber: String) =
    mDevice.findObject(
        UiSelector()
            .resourceId("$packageName:id/credit_card_number")
            .textContains(creditCardNumber),
    )

private fun siteSecurityToolbarButton() =
    mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_security_indicator"))

private fun clickPageObject(webPageItem: UiObject) {
    for (i in 1..RETRY_COUNT) {
        try {
            webPageItem.also {
                it.waitForExists(waitingTime)
                it.click()
            }

            break
        } catch (e: UiObjectNotFoundException) {
            if (i == RETRY_COUNT) {
                throw e
            } else {
                browserScreen {
                }.openThreeDotMenu {
                }.refreshPage {
                    progressBar.waitUntilGone(waitingTime)
                }
            }
        }
    }
}

fun longClickPageObject(webPageItem: UiObject) {
    for (i in 1..RETRY_COUNT) {
        try {
            webPageItem.also {
                it.waitForExists(waitingTime)
                it.longClick()
            }

            break
        } catch (e: UiObjectNotFoundException) {
            if (i == RETRY_COUNT) {
                throw e
            } else {
                browserScreen {
                }.openThreeDotMenu {
                }.refreshPage {
                }
                progressBar.waitUntilGone(waitingTime)
            }
        }
    }
}

private fun webPageItemContainingText(itemText: String) =
    mDevice.findObject(UiSelector().textContains(itemText))

private fun webPageItemWithText(itemText: String) =
    mDevice.findObject(UiSelector().text(itemText))

private fun webPageItemWithResourceId(resourceId: String) =
    mDevice.findObject(UiSelector().resourceId(resourceId))

private fun webPageItemContainingTextAndResourceId(resourceId: String, itemText: String) =
    mDevice.findObject(
        UiSelector()
            .resourceId(resourceId)
            .textContains(itemText),
    )

private fun setPageObjectText(webPageItem: UiObject, text: String) {
    for (i in 1..RETRY_COUNT) {
        try {
            webPageItem.also {
                it.waitForExists(waitingTime)
                it.setText(text)
            }

            break
        } catch (e: UiObjectNotFoundException) {
            if (i == RETRY_COUNT) {
                throw e
            } else {
                browserScreen {
                }.openThreeDotMenu {
                }.refreshPage {
                    progressBar.waitUntilGone(waitingTime)
                }
            }
        }
    }
}

private val currentDate = LocalDate.now()
private val currentDay = currentDate.dayOfMonth
private val currentMonth = currentDate.month
private val currentYear = currentDate.year
