import logging
import os
import re
import time
import urllib.parse
from pathlib import Path
from typing import Optional

from selenium.common.exceptions import ElementClickInterceptedException

from ibeam.src.login.driver import release_chrome_driver, save_screenshot, DriverFactory
from ibeam.src.utils.selenium_utils import any_of
from ibeam.src.two_fa_handlers.two_fa_handler import TwoFaHandler

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait

_LOGGER = logging.getLogger('ibeam.' + Path(__file__).stem)

_GOOG_QR_CODE_CLASS = os.environ.get('IBEAM_GOOG_QR_CODE_CLASS', 'bigger-qr-code')
"""HTML element indicating web messages needs authorization."""

_GOOG_QR_CODE_DATA = os.environ.get('IBEAM_GOOG_QR_CODE_DATA', 'qr-code')
"""HTML data attribute with the qr code."""

_GOOG_AUTH_REMEMBER_CLASS = os.environ.get('IBEAM_GOOG_AUTH_REMEMBER_CLASS', 'local-storage-checkbox')
"""HTML element to remember web messages device pairing."""

_GOOG_MESSAGES_LIST_CLASS = os.environ.get('IBEAM_GOOG_MESSAGES_LIST_CLASS', '.text-content.unread .snippet-text')
"""HTML element indicating web messages has loaded."""

_GOOG_2FA_HEADING = os.environ.get('IBEAM_GOOG_2FA_HEADING', 'Your requested authentication code')
"""HTML element text indicating 2fa message received."""

_GOOG_MESSAGE_CLICK_RETRIES = int(os.environ.get('IBEAM_GOOG_MESSAGE_CLICK_RETRIES', 5))
"""How many times to try marking the message as read."""


class GoogleMessagesTwoFaHandler(TwoFaHandler):

    def __init__(self, driver_factory:DriverFactory, *args, **kwargs):
        self.driver_factory = driver_factory
        super().__init__(*args, **kwargs)

    def get_two_fa_code(self, _) -> Optional[str]:
        code_two_fa = None

        driver_2fa = self.driver_factory.new_driver(name='google_msg', incognito=False)
        if driver_2fa is None:
            return None

        try:
            driver_2fa.get('https://messages.google.com/web')

            sms_auth_present = EC.presence_of_element_located((By.CLASS_NAME, _GOOG_QR_CODE_CLASS))
            sms_code_present = EC.text_to_be_present_in_element((By.CSS_SELECTOR, _GOOG_MESSAGES_LIST_CLASS),
                                                                _GOOG_2FA_HEADING)

            WebDriverWait(driver_2fa, 240).until(any_of(sms_auth_present, sms_code_present))

            sms_auth_el = driver_2fa.find_elements(By.CLASS_NAME, _GOOG_QR_CODE_CLASS)

            if sms_auth_el:
                driver_2fa.find_element(By.CLASS_NAME, _GOOG_AUTH_REMEMBER_CLASS).click()

                data = urllib.parse.quote(sms_auth_el[0].get_attribute('data-' + _GOOG_QR_CODE_DATA))

                _LOGGER.info(
                    'Web messages is not authenticated. Open this URL to pair web messages with your android phone:')
                _LOGGER.info(
                    f'http://api.qrserver.com/v1/create-qr-code/?color=000000&bgcolor=FFFFFF&qzone=1&margin=0&size=400x400&ecc=L&data={data}')

                WebDriverWait(driver_2fa, 120).until(sms_code_present)

            sms_list_el = driver_2fa.find_elements(By.CSS_SELECTOR, _GOOG_MESSAGES_LIST_CLASS)

            if not sms_list_el:
                _LOGGER.error('Timeout or authentication error while loading sms messages.')
                save_screenshot(driver_2fa, self.outputs_dir, postfix='__google_2fa')
            else:
                _LOGGER.info(f'First SMS found: "{sms_list_el[0].text}"')

                code_two_fa = re.search(r'(\d+)', sms_list_el[0].text).group(1)

                _LOGGER.info('Waiting for SMS message to be visible')
                WebDriverWait(driver_2fa, 30).until(EC.visibility_of(sms_list_el[0]))

                clicked_ok = False
                for i in range(_GOOG_MESSAGE_CLICK_RETRIES):
                    try:
                        sms_list_el[0].click()  # mark message as read
                        clicked_ok = True
                        _LOGGER.info('SMS message marked as read')
                        break
                    except ElementClickInterceptedException as e:
                        if isinstance(e, ElementClickInterceptedException) \
                                and 'Other element would receive the click' in str(e):
                            _LOGGER.warning(f'Failed marking SMS message as read due to obstructing elements')
                        else:
                            _LOGGER.exception(f'Exception while marking SMS message as read: {e}')

                        save_screenshot(driver_2fa, self.outputs_dir, postfix='__google_2fa')

                        _LOGGER.info(f'Retrying clicking SMS message {_GOOG_MESSAGE_CLICK_RETRIES - i - 1} more times.')
                        time.sleep(2)

                if not clicked_ok:
                    _LOGGER.warning('Failed all attempts to mark SMS message as read')

                time.sleep(2)  # wait for click to mark message as read

        except:
            save_screenshot(driver_2fa, self.outputs_dir, '__google-msg')
            raise
        finally:
            _LOGGER.info(f'Cleaning up the resources. Google MSG Driver: {driver_2fa}')
            release_chrome_driver(driver_2fa)

        return code_two_fa

    def __str__(self):
        return f"GoogleMessagesTwoFaHandler(driver_path={self.driver_factory.driver_path})"
