"""Pages associated with pairing process of Fitbit tracker device."""
import shlex
from typing import List, Optional
from xml.dom import minidom

from blueberry.utils.ui_pages import errors
from blueberry.utils.ui_pages import ui_core
from blueberry.utils.ui_pages import ui_node
from blueberry.utils.ui_pages.fitbit_companion import constants

# Alias for typing convenience.
_NodeList = List[ui_node.UINode]


class PairRetryPage(ui_core.UIPage):
    """Fitbit Companion App's page for retry of pairing."""

    PAGE_RE_TEXT = 'TRY AGAIN'

    def retry(self) -> ui_core.UIPage:
        """Clicks button to retry pairing.

    Returns:
      The transformed page.

    Raises:
      errors.UIError: Fail to find the target node.
    """
        return self.click_node_by_text('TRY AGAIN')


class Pairing4DigitPage(ui_core.UIPage):
    """Fitbit Companion App's page to enter 4 digit pins for pairing."""

    PAGE_RID = f'{constants.PKG_NAME_ID}/digits'
    NODE_DIGIT_RIDS = (f'{constants.PKG_NAME_ID}/digit0'
                       f'{constants.PKG_NAME_ID}/digit1'
                       f'{constants.PKG_NAME_ID}/digit2'
                       f'{constants.PKG_NAME_ID}/digit3')

    def input_pins(self, pins: str) -> ui_core.UIPage:
        """Inputs 4 digit pins required in pairing process.

    Args:
      pins: 4 digit pins (e.g.: "1234")

    Returns:
      The transformed page.

    Raises:
      ValueError: Input pins is not valid.
    """
        if len(pins) != 4:
            raise ValueError(f'4 digits required here! (input={pins})')

        for digit in pins:
            self.ctx.ad.adb.shell(shlex.split(f'input text "{digit}"'))

        return self.ctx.page


class PairingConfirmPage(ui_core.UIPage):
    """Fitbit Companion App's page to confirm pairing."""

    NODE_ALLOW_ACCESS_TEXT = 'Allow access to your contacts and call history'
    NODE_PAIR_TEXT = 'Pair'

    @classmethod
    def from_xml(cls, ctx: ui_core.Context, ui_xml: minidom.Document, clickable_nodes: _NodeList,
                 enabled_nodes: _NodeList, all_nodes: _NodeList) -> Optional[ui_core.UIPage]:
        """Instantiates page object from XML object.

    Args:
      ctx: Page context object.
      ui_xml: Parsed XML object.
      clickable_nodes: Clickable node list from page.
      enabled_nodes: Enabled node list from page.
      all_nodes: All node from page.

    Returns:
      UI page object iff the given XML object can be parsed.
    """
        for node in enabled_nodes:
            if (node.text == cls.NODE_PAIR_TEXT and node.resource_id == 'android:id/button1'):
                return cls(ctx, ui_xml, clickable_nodes, enabled_nodes, all_nodes)

    def confirm(self) -> ui_core.UIPage:
        """Confirms the action of pairing.

    Returns:
      The transformed page.
    """
        self.click_node_by_text(self.NODE_ALLOW_ACCESS_TEXT)

        return self.click_node_by_text(self.NODE_PAIR_TEXT)


class PairingIntroPage(ui_core.UIPage):
    """Fitbit Companion App's pages for introduction of product usage."""

    NODE_TITLE_TEXT_SET = frozenset([
        'All set!',
        'Double tap to wake',
        'Firmly double-tap',
        'How to go back',
        'Swipe down',
        'Swipe left or right',
        'Swipe to navigate',
        'Swipe up',
        'Try it on',
        'Wear & care tips',
    ])
    NODE_NEXT_BTN_RID = f'{constants.PKG_NAME_ID}/btn_next'

    @classmethod
    def from_xml(cls, ctx: ui_core.Context, ui_xml: minidom.Document, clickable_nodes: _NodeList,
                 enabled_nodes: _NodeList, all_nodes: _NodeList) -> Optional[ui_core.UIPage]:
        """Instantiates page object from XML object.

    The appending punctuation '.' of the text will be ignored during comparison.

    Args:
      ctx: Page context object.
      ui_xml: Parsed XML object.
      clickable_nodes: Clickable node list from page.
      enabled_nodes: Enabled node list from page.
      all_nodes: All node from page.

    Returns:
      UI page object iff the given XML object can be parsed.
    """
        for node in enabled_nodes:
            node_text = node.text[:-1] if node.text.endswith('.') else node.text
            if node_text in cls.NODE_TITLE_TEXT_SET:
                return cls(ctx, ui_xml, clickable_nodes, enabled_nodes, all_nodes)

    def next(self):
        """Moves to next page."""
        return self.click_node_by_rid(self.NODE_NEXT_BTN_RID)


class PairAndLinkPage(ui_core.UIPage):
    """Fitbit Companion App's landing page for pairing and linking."""

    PAGE_TEXT = 'Bluetooth Pairing and Linking'
    NODE_CANCEL_TEXT = 'Cancel'

    def cancel(self) -> ui_core.UIPage:
        """Cancel pairing process.

    Returns:
      The transformed page.
    """
        return self.click_node_by_text(self.NODE_CANCEL_TEXT)


class PremiumPage(ui_core.UIPage):
    """Fitbit Companion App's page for Premium information."""

    PAGE_TEXT = 'See all Premium features'
    NODE_EXIT_IMG_BTN_CLASS = 'android.widget.ImageButton'

    def done(self):
        """Completes pairing process.

    Returns:
      The transformed page.
    """
        return self.click_node_by_class(self.NODE_EXIT_IMG_BTN_CLASS)


class PairPrivacyConfirmPage(ui_core.UIPage):
    """Fitbit Companion App's page to confirm the privacy befoe pairing."""

    PAGE_RID = f'{constants.PKG_NAME_ID}/gdpr_scroll_view'
    _NODE_ACCEPT_BTN_TEXT = 'ACCEPT'
    _SWIPE_RETRY = 5

    def accept(self) -> ui_core.UIPage:
        """Accepts the privacy policy.

    Returns:
      The transformed page.

    Raises:
      errors.UIError: Fail to get target node.
    """
        self.swipe_down()
        for i in range(self._SWIPE_RETRY):
            node = self.get_node_by_text(self._NODE_ACCEPT_BTN_TEXT)
            if node is None:
                raise errors.UIError(
                    f'Fail to find the node with text={self._NODE_ACCEPT_BTN_TEXT}')

            atr_obj = node.attributes.get('enabled')
            if atr_obj is not None and atr_obj.value == 'true':
                return self.click_node_by_text(self._NODE_ACCEPT_BTN_TEXT)

            self.log.debug('swipe down to browse the privacy info...%d', i + 1)
            self.swipe_down()
            self.ctx.get_page()

        raise errors.UIError('Fail to wait for the enabled button to confirm the privacy!')


class CancelPairPage(ui_core.UIPage):
    """Fitbit Companion App's page to confirm the cancel of pairing."""

    PAGE_TEXT = ('Canceling this process may result in poor connectivity with '
                 'your Fitbit device.')
    NODE_YES_TEXT = 'YES'
    NODE_NO_TEXT = 'NO'

    def yes(self) -> ui_core.UIPage:
        """Cancels the pairing process.

    Returns:
      The transformed page.

    Raises:
      errors.UIError: Fail to get target node.
    """
        return self.click_node_by_text(self.NODE_YES_TEXT)

    def no(self) -> ui_core.UIPage:
        """Continues the pairing process.

    Returns:
      The transformed page.

    Raises:
      errors.UIError: Fail to get target node.
    """
        return self.click_node_by_text(self.NODE_NO_TEXT)


class CancelPair2Page(ui_core.UIPage):
    """Fitbit Companion App's page to confirm the cancel of pairing."""

    PAGE_TEXT = ('Are you sure you want to cancel pairing?'
                 ' You can set up your Fitbit Device later on the Devices screen.')

    _NODE_YES_TEXT = 'CANCEL'

    def yes(self) -> ui_core.UIPage:
        """Cancels the pairing process.

    Returns:
      The transformed page.

    Raises:
      errors.UIError: Fail to get target node.
    """
        return self.click_node_by_text(self._NODE_YES_TEXT)


class ConfirmReplaceSmartWatchPage(ui_core.UIPage):
    """Fitbit Companion App's page to confirm the replacement of tracker device.

  When you already have one paired tracker device and you try to pair a
  new one, this page will show up.
  """
    NODE_SWITCH_BTN_TEXT = 'SWITCH TO'
    PAGE_TEXT = 'Switching?'

    def confirm(self) -> ui_core.UIPage:
        """Confirms the switching.

    Returns:
      The transformed page.

    Raises:
      errors.UIError: Fail to get target node.
    """

        def _search_switch_btn_node(node: ui_node.UINode) -> bool:
            if node.text.startswith(self.NODE_SWITCH_BTN_TEXT):
                return True

            return False

        node = self.get_node_by_func(_search_switch_btn_node)
        if node is None:
            raise errors.UIError('Failed to confirm the switching of new tracker device!')

        return self.click(node)


class ConfirmChargePage(ui_core.UIPage):
    """Fitbit Companion App's page to confirm the charge condition."""

    PAGE_RE_TEXT = 'Let your device charge during setup'

    def next(self) -> ui_core.UIPage:
        """Forwards to pairing page.

    Returns:
      The transformed page.

    Raises:
      errors.UIError: Fail to get target node.
    """
        return self.click_node_by_text('NEXT')


class ChooseTrackerPage(ui_core.UIPage):
    """Fitbit Companion App's page to select device model for pairing."""

    ACTIVITY = f'{constants.PKG_NAME}/com.fitbit.device.ui.setup.choose.ChooseTrackerActivity'
    PAGE_RID = f'{constants.PKG_NAME_ID}/choose_tracker_title_container'

    def select_device(self, name: str) -> ui_core.UIPage:
        """Selects tracker device.

    Args:
      name: The name of device. (e.g.: 'Buzz')

    Returns:
      The transformed page.

    Raises:
      errors.UIError: Fail to get target node.
    """
        return self.click_node_by_text(name)


class ConfirmDevicePage(ui_core.UIPage):
    """Fitbit Companion App's page to confirm the selected tracker device."""
    PAGE_TEXT = 'SET UP'
    ACTIVITY = f'{constants.PKG_NAME}/com.fitbit.device.ui.setup.choose.ConfirmDeviceActivity'

    def confirm(self) -> ui_core.UIPage:
        """Confirms the selection.

    Returns:
      The transformed page.

    Raises:
      errors.UIError: Fail to get target node.
    """
        return self.click_node_by_text(self.PAGE_TEXT)


class SkipInfoPage(ui_core.UIPage):
    """Fitbit Companion App's page to skip the 'not working' page."""

    PAGE_TEXT = 'Skip Information Screens'
    NODE_SKIP_TEXT = 'SKIP'
    NODE_CONTINUE_TEXT = 'CONTINUE'

    def skip(self) -> ui_core.UIPage:
        """Skips the information screens.

    Returns:
      The transformed page.
    """
        return self.click_node_by_text(self.NODE_SKIP_TEXT)


class UpdateDevicePage(ui_core.UIPage):
    """Fitbit Companion App's page to update device."""

    PAGE_TEXT = 'INSTALL UPDATE NOW'
    NODE_UPDATE_LATER_BTN_TEXT = 'UPDATE LATER'

    def update_later(self) -> ui_core.UIPage:
        """Cancels the update.

    Returns:
      The transformed page.
    """
        return self.click_node_by_text(self.NODE_UPDATE_LATER_BTN_TEXT)


class PurchasePage(ui_core.UIPage):
    """Fitbit Companion App's page to purchase merchandise."""

    PAGE_RE_TEXT = 'Protect Your New Device'
    NODE_SKIP_BTN_TEXT = 'NOT NOW'

    def skip(self) -> ui_core.UIPage:
        """Skips the purchase action.

    Returns:
      The transformed page.
    """
        return self.click_node_by_text(self.NODE_SKIP_BTN_TEXT)
