import logging

from selenium.common.exceptions import TimeoutException
from selenium.webdriver import ActionChains, Firefox
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions as EC

from modules.classes.bookmark import Bookmark
from modules.page_base import BasePage
from modules.util import BrowserActions


class Navigation(BasePage):
    """Page Object Model for nav buttons, AwesomeBar and toolbar"""

    URL_TEMPLATE = "about:blank"
    BROWSER_MODES = {
        "Bookmarks": "*",
        "Tabs": "%",
        "History": "^",
        "Actions": ">",
    }
    VALID_SEARCH_MODES = {
        "Google",
        "eBay",
        "Amazon.com",
        "Bing",
        "DuckDuckGo",
        "Wikipedia (en)",
    }

    def __init__(self, driver: Firefox, **kwargs):
        super().__init__(driver, **kwargs)
        self.search_bar = None
        self.awesome_bar = None
        self.change_search_settings_button = None

    @BasePage.context_content
    def expect_in_content(self, condition) -> BasePage:
        """Like BasePage.expect, but guarantee we're looking at CONTEXT_CONTENT"""
        self.wait.until(condition)
        return self

    @BasePage.context_chrome
    def set_awesome_bar(self) -> BasePage:
        """Set the awesome_bar attribute of the Navigation object"""
        self.awesome_bar = self.get_element("awesome-bar")
        return self

    @BasePage.context_chrome
    def get_awesome_bar(self) -> WebElement:
        """Get the Awesome Bar. Prefer this over get_element."""
        self.set_awesome_bar()
        return self.awesome_bar

    @BasePage.context_chrome
    def get_awesome_bar_text(self):
        """
        Get the text directly from the awesome bar.
        This is different from 'driver.current_url' which pulls from href
        """
        awesome_bar = self.get_element("awesome-bar").get_attribute("value")
        return awesome_bar

    @BasePage.context_chrome
    def clear_awesome_bar(self) -> BasePage:
        """Clear the Awesome Bar. Prefer this over get_element("awesome-bar").clear()"""
        self.set_awesome_bar()
        self.awesome_bar.clear()
        return self

    @BasePage.context_chrome
    def type_in_awesome_bar(self, term: str) -> BasePage:
        """Enter text into the Awesome Bar. You probably want self.search()"""
        self.set_awesome_bar()
        self.awesome_bar.click()
        self.awesome_bar.send_keys(term)
        return self

    def set_search_mode_via_awesome_bar(self, mode: str) -> BasePage:
        """
        Given a `mode`, set the Awesome Bar search mode. Returns self.

        Parameter
        ---------
        mode: str
            The name of the search mode, or the keystroke shortcut (e.g. ^ for History)
        """
        if mode in self.BROWSER_MODES:
            abbr = self.BROWSER_MODES[mode]
        else:
            abbr = mode.lower()[:2]
        self.type_in_awesome_bar(abbr)
        self.wait.until(
            EC.visibility_of_element_located(
                self.get_selector("tab-to-search-text-span")
            )
        )
        self.awesome_bar.send_keys(Keys.TAB)
        self.wait.until(
            EC.text_to_be_present_in_element(
                self.get_selector("search-mode-span"), mode
            )
        )
        return self

    @BasePage.context_chrome
    def search(self, term: str, mode=None) -> BasePage:
        """
        Search using the Awesome Bar, optionally setting the search mode first. Returns self.

        Attributes
        ----------

        term : str
            The search term

        mode : str | None
            If set, the name or keystroke shortcut of the search mode
        """
        if mode:
            self.set_search_mode_via_awesome_bar(mode).type_in_awesome_bar(
                term + Keys.ENTER
            )
        else:
            self.type_in_awesome_bar(term + Keys.ENTER)
        return self

    @BasePage.context_chrome
    def set_search_bar(self) -> BasePage:
        """Set the search_bar attribute of the Navigation object"""
        self.search_bar = self.find_element(By.CLASS_NAME, "searchbar-textbox")
        return self

    @BasePage.context_chrome
    def search_bar_search(self, term: str) -> BasePage:
        """
        Search using the *Old* Search Bar. Returns self.

        Attributes
        ----------

        term : str
            The search term
        """
        self.search_bar = self.find_element(By.CLASS_NAME, "searchbar-textbox")
        self.search_bar.click()
        self.search_bar.send_keys(term + Keys.ENTER)
        return self

    @BasePage.context_chrome
    def type_in_search_bar(self, term: str) -> BasePage:
        """
        Type in the *Old* Search Bar. Returns self.

        Attributes
        ----------

        term : str
            The search term
        """
        self.search_bar = self.find_element(By.CLASS_NAME, "searchbar-textbox")
        self.search_bar.click()
        self.search_bar.send_keys(term)
        return self

    def open_awesome_bar_settings(self):
        """Open search settings from the awesome bar"""
        self.click_on("search-settings")
        return self

    @BasePage.context_chrome
    def click_on_change_search_settings_button(self) -> BasePage:
        self.search_bar = self.find_element(By.CLASS_NAME, "searchbar-textbox")
        self.search_bar.click()
        self.change_search_settings_button = self.find_element(
            By.ID, "searchbar-anon-search-settings"
        )
        self.change_search_settings_button.click()
        return self

    @BasePage.context_chrome
    def click_in_awesome_bar(self) -> BasePage:
        self.set_awesome_bar()
        self.awesome_bar.click()
        return self

    @BasePage.context_chrome
    def click_search_mode_switcher(self) -> BasePage:
        """
        click search mode switcher
        """
        self.search_mode_switcher = self.get_element("searchmode-switcher")
        self.search_mode_switcher.click()
        return self

    @BasePage.context_chrome
    def set_search_mode(self, search_mode: str) -> BasePage:
        """
        set new search location if search_mode in VALID_SEARCH_MODES

        Parameter:
            search_mode (str): search mode to be selected

        Raises:
            StopIteration: if a valid search mode is not found in the list of valid elements.
        """
        # check if search_mode is valid, otherwise raise error.
        if search_mode not in self.VALID_SEARCH_MODES:
            raise ValueError("search location is not valid.")
        # switch to chrome context
        # get list of all valid search modes and filter by label
        self.get_element("search-mode-switcher-option", labels=[search_mode]).click()
        return self

    def context_click_in_awesome_bar(self) -> BasePage:
        self.set_awesome_bar()
        actions = ActionChains(self.driver)
        actions.context_click(self.awesome_bar).perform()
        return self

    @BasePage.context_chrome
    def get_download_button(self) -> WebElement:
        """
        Gets the download button WebElement
        """
        downloads_button = self.get_element("downloads-button")
        return downloads_button

    def click_download_button(self) -> BasePage:
        self.get_download_button().click()
        return self

    @BasePage.context_chrome
    def wait_for_download_animation_finish(
        self, downloads_button: WebElement
    ) -> BasePage:
        """
        Waits for the download button to finish playing the animation for downloading to local computer
        """
        try:
            self.wait.until(
                lambda _: downloads_button.get_attribute("notification") == "finish"
            )
        except TimeoutException:
            logging.warning("Animation did not finish or did not play.")
        return self

    @BasePage.context_chrome
    def open_tracker_panel(self) -> BasePage:
        """
        Clicks the shield icon and opens the panel associated with it
        """
        self.get_element("shield-icon").click()
        return self

    def wait_for_item_to_download(self, filename: str) -> BasePage:
        """
        Check the downloads tool in the toolbar to wait for a given file to download
        """
        original_timeout = self.driver.timeouts.implicit_wait
        try:
            # Whatever our timeout, we want to lengthen it because downloads
            self.driver.implicitly_wait(original_timeout * 2)
            self.element_visible("downloads-item-by-file", labels=[filename])
            self.expect_not(
                EC.element_attribute_to_include(
                    self.get_selector("downloads-button"), "animate"
                )
            )
            with self.driver.context(self.context_id):
                self.driver.execute_script(
                    "arguments[0].setAttribute('hidden', true)",
                    self.get_element("downloads-button"),
                )
        finally:
            self.driver.implicitly_wait(original_timeout)
        return self

    @BasePage.context_chrome
    def refresh_page(self) -> BasePage:
        """
        Refreshes the current page by clicking the refresh button in the browser.
        """

        self.get_element("refresh-button").click()
        self.wait_for_page_to_load()
        return self

    def handle_geolocation_prompt(self, button_type="primary"):
        """
        Handles geolocation prompt by clicking either the 'Allow' or 'Block' button based on the button_type provided
        """
        button_selector = f"popup-notification-{button_type}-button"
        self.element_clickable(button_selector)
        self.click_on(button_selector)

    def open_searchmode_switcher_settings(self):
        """Open search settings from searchmode switcher in awesome bar"""
        self.click_on("searchmode-switcher")
        self.click_on("searchmode-switcher-settings")
        return self

    @BasePage.context_chrome
    def select_element_in_nav(self, element: str) -> BasePage:
        self.get_element(element).click()
        return self

    # Bookmark

    @BasePage.context_chrome
    def add_bookmark_via_star_icon(self) -> BasePage:
        """
        Bookmark a site via star button and click save on the bookmark panel
        """
        self.get_element("star-button").click()
        self.get_element("save-bookmark-button").click()
        return self

    @BasePage.context_chrome
    def verify_star_button_is_blue(self) -> BasePage:
        """
        Verifies that the star button is blue (indicating a bookmarked page)
        """
        self.element_visible("blue-star-button")
        return self

    @BasePage.context_chrome
    def bookmark_page_other(self) -> BasePage:
        self.get_element("star-button").click()
        dropdown = self.get_element("bookmarks-type-dropdown")
        dropdown.click()
        self.get_element("bookmarks-type-dropdown-other").click()
        dropdown.click()
        self.get_element("save-bookmark-button").click()
        return self

    @BasePage.context_chrome
    def add_bookmark_advanced(
        self, bookmark_data: Bookmark, ba: BrowserActions
    ) -> BasePage:
        iframe = self.get_element("bookmark-iframe")
        ba.switch_to_iframe_context(iframe)
        # fill name
        if bookmark_data.name is not None:
            self.actions.send_keys(bookmark_data.name).perform()
        self.actions.send_keys(Keys.TAB).perform()
        # fill url
        self.actions.send_keys(bookmark_data.url + Keys.TAB).perform()
        # fill tags
        if bookmark_data.tags is not None:
            self.actions.send_keys(bookmark_data.tags).perform()
        self.actions.send_keys(Keys.TAB).perform()
        # fill keywords
        if bookmark_data.keyword is not None:
            self.actions.send_keys(bookmark_data.keyword).perform()
        self.actions.send_keys(Keys.TAB, Keys.TAB, Keys.TAB, Keys.ENTER).perform()
        ba.switch_to_content_context()
        return self

    @BasePage.context_chrome
    def toggle_bookmarks_toolbar_with_key_combo(self) -> BasePage:
        """Use Cmd/Ctrl + B to open the Print Preview, wait for load"""

        if self.sys_platform() == "Darwin":
            mod_key = Keys.COMMAND
        else:
            mod_key = Keys.CONTROL
        self.perform_key_combo(mod_key, Keys.SHIFT, "b")
        return self

    @BasePage.context_chrome
    def confirm_bookmark_exists(self, match_string: str) -> BasePage:
        """
        For a given string, return self if it exists in the label of a bookmark, else assert False.
        """

        bookmarks = self.get_elements("bookmark-in-bar")
        logging.info(f"Found {len(bookmarks)} bookmarks.")
        for el in bookmarks:
            logging.info(el.get_attribute("label"))

        matches_short_string = any(
            [match_string in el.get_attribute("label") for el in bookmarks]
        )
        matches_long_string = any(
            [el.get_attribute("label") in match_string for el in bookmarks]
        )
        assert matches_short_string or matches_long_string
        return self
