from dataclasses import dataclass
from functools import cached_property
import json
from pathlib import Path
import subprocess
import re


import psutil
from selenium import webdriver


@dataclass
class AddonHelper:
    driver: webdriver.Remote

    @cached_property
    def addon_id(self) -> str:
        return get_addon_id(driver=self.driver)

    @property
    def extension_prefix(self) -> str:
        protocol = {
            'chrome': 'chrome-extension',
            'firefox': 'moz-extension',
        }[self.driver.name]
        return f'{protocol}://{self.addon_id}'

    def open_page(self, path: str) -> None:
        self.driver.get(self.extension_prefix + '/' + path)


# NOTE looks like it used to be posssible in webdriver api?
# at least as of 2011 https://github.com/gavinp/chromium/blob/681563ea0f892a051f4ef3d5e53438e0bb7d2261/chrome/test/webdriver/test/chromedriver.py#L35-L40
# but here https://github.com/SeleniumHQ/selenium/blob/master/cpp/webdriver-server/command_types.h there are no Extension commands
# also see driver.command_executor._commands
def _get_chrome_addon_id(driver: webdriver.Remote) -> str:
    """
    For a temporary addon extension id is autogenerated, so we need to extract it every time
    """
    user_data_dir = Path(driver.capabilities['chrome']['userDataDir'])
    prefs_file = user_data_dir / 'Default/Preferences'
    assert prefs_file.exists(), prefs_file

    # for some idiotic reason, after chrome launches, extension settings aren't immediately available
    # this can take up to 30 secons in this loop until they are populated
    while True:
        prefs = json.loads(prefs_file.read_text())
        extension_settings = prefs.get('extensions', {}).get('settings', None)
        if extension_settings is not None:
            # there are some other weird builtin extension as well
            # this seems like the easiest way to filter them out extracing by extension name or path
            [addon_id] = [k for k, v in extension_settings.items() if v['creation_flags'] != 1]
            return addon_id


def _get_firefox_addon_id(driver: webdriver.Remote) -> str:
    moz_profile = Path(driver.capabilities['moz:profile'])
    prefs_file = moz_profile / 'prefs.js'
    assert prefs_file.exists(), prefs_file

    while True:
        for line in prefs_file.read_text().splitlines():
            m = re.fullmatch(r'user_pref\("extensions.webextensions.uuids", "(.*)"\);', line)
            if m is None:
                continue
            # this contains a json with escaped quotes
            user_prefs_s = m.group(1).replace('\\', '')
            user_prefs = json.loads(user_prefs_s)
            addon_ids = [v for k, v in user_prefs.items() if 'mozilla.' not in k]
            if len(addon_ids) == 0:
                # for some stupid reason it doesn't appear immediately in the file
                continue
            [addon_id] = addon_ids
            return addon_id


def get_addon_id(driver: webdriver.Remote) -> str:
    extractor = {
        'firefox': _get_firefox_addon_id,
        'chrome': _get_chrome_addon_id,
    }[driver.name]
    return extractor(driver)


def get_window_id(driver: webdriver.Remote) -> str:
    if driver.name == 'firefox':
        pid = str(driver.capabilities['moz:processID'])
    elif driver.name == 'chrome':
        # ugh no pid in capabilities...
        driver_pid = driver.service.process.pid  # type: ignore[attr-defined]
        process = psutil.Process(driver_pid)
        [chrome_process] = process.children()
        cmdline = chrome_process.cmdline()
        assert '--enable-automation' in cmdline, cmdline
        pid = str(chrome_process.pid)
    else:
        raise RuntimeError(driver.name)
    return get_wid_by_pid(pid)


def get_wid_by_pid(pid: str) -> str:
    # https://askubuntu.com/a/385037/427470
    wids = subprocess.check_output(['xdotool', 'search', '--pid', pid]).decode('utf8').splitlines()
    wids = [w.strip() for w in wids if len(w.strip()) > 0]

    def has_wm_desktop(wid: str) -> bool:
        # TODO no idea why is that important. found out experimentally
        out = subprocess.check_output(['xprop', '-id', wid, '_NET_WM_DESKTOP']).decode('utf8')
        return 'not found' not in out

    [wid] = filter(has_wm_desktop, wids)
    return wid


def focus_browser_window(driver: webdriver.Remote) -> None:
    # FIXME assert not is_headless(driver)  # just in case
    wid = get_window_id(driver)
    subprocess.check_call(['xdotool', 'windowactivate', '--sync', wid])
