import logging
import json
import os
import webbrowser

from legendary import __version__

logger = logging.getLogger('WebViewHelper')
webview_available = True

try:
    import webview

    # silence logger
    webview.logger.setLevel(logging.FATAL)
    gui = webview.initialize()
    if gui and os.name == 'nt' and gui.renderer not in ('edgechromium', 'cef'):
        raise NotImplementedError(f'Renderer {gui.renderer} not supported on Windows.')
except Exception as e:
    logger.debug(f'Webview unavailable, disabling webview login (Exception: {e!r}).')
    webview_available = False

login_url = 'https://www.epicgames.com/id/login'
sid_url = 'https://www.epicgames.com/id/api/redirect?'
logout_url = f'https://www.epicgames.com/id/logout?productName=epic-games&redirectUrl={login_url}'
goodbye_url = 'https://legendary.gl/goodbye'
window_js = '''
window.ue = {
    signinprompt: {
        requestexchangecodesignin: pywebview.api.set_exchange_code,
        registersignincompletecallback: pywebview.api.trigger_sid_exchange
    },
    common: {
        launchexternalurl: pywebview.api.open_url_external
    }
}
'''

get_sid_js = '''
function on_loaded() {
    pywebview.api.login_sid(this.responseText);
}

var sid_req = new XMLHttpRequest();
sid_req.addEventListener("load", on_loaded);
sid_req.open("GET", "/id/api/redirect?");
sid_req.send();
'''


class MockLauncher:
    def __init__(self, callback_sid, callback_code):
        self.callback_sid = callback_sid
        self.callback_code = callback_code
        self.window = None
        self.inject_js = True
        self.destroy_on_load = False
        self.callback_result = None

    def on_loaded(self):
        url = self.window.get_current_url()
        logger.debug(f'Loaded url: {url.partition("?")[0]}')

        if self.destroy_on_load:
            logger.info('Closing login window...')
            self.window.destroy()
            return

        # Inject JS so required window.ue stuff is available
        if self.inject_js:
            self.window.evaluate_js(window_js)

        if 'logout' in url and self.callback_sid:
            # prepare to close browser after logout redirect
            self.destroy_on_load = True
        elif 'logout' in url:
            self.inject_js = True

    def nop(self, *args, **kwargs):
        return

    def open_url_external(self, url):
        webbrowser.open(url)

    def set_exchange_code(self, exchange_code):
        self.inject_js = False
        logger.debug('Got exchange code (stage 1)!')
        # The default Windows webview retains cookies, GTK/Qt do not. Therefore we can
        # skip logging out on those platforms and directly use the exchange code we're given.
        # On windows we have to do a little dance with the SID to create a session that
        # remains valid after logging out in the embedded browser.
        #  Update: Epic broke SID login, we'll also do this on Windows now
        # if self.window.gui.renderer in ('gtkwebkit2', 'qtwebengine', 'qtwebkit'):
        self.destroy_on_load = True
        try:
            self.callback_result = self.callback_code(exchange_code)
        except Exception as e:
            logger.error(f'Logging in via exchange-code failed with {e!r}')
        finally:
            # We cannot destroy the browser from here,
            # so we'll load a small goodbye site first.
            self.window.load_url(goodbye_url)

    def trigger_sid_exchange(self, *args, **kwargs):
        # check if code-based login hasn't already set the destroy flag
        if not self.destroy_on_load:
            logger.debug('Injecting SID JS')
            # inject JS to get SID API response and call our API
            self.window.evaluate_js(get_sid_js)

    def login_sid(self, sid_json):
        # Try SID login, then log out
        try:
            j = json.loads(sid_json)
            sid = j['sid']
            logger.debug(f'Got SID (stage 2)! Executing sid login callback...')
            exchange_code = self.callback_sid(sid)
            if exchange_code:
                self.callback_result = self.callback_code(exchange_code)
        except Exception as e:
            logger.error(f'SID login failed with {e!r}')
        finally:
            logger.debug('Starting browser logout...')
            self.window.load_url(logout_url)


def do_webview_login(callback_sid=None, callback_code=None, user_agent=None):
    api = MockLauncher(callback_sid=callback_sid, callback_code=callback_code)
    url = login_url

    if os.name == 'nt':
        # On Windows we open the logout URL first to invalidate the current cookies (if any).
        # Additionally, we have to disable JS injection for the first load, as otherwise the user
        # will get an error for some reason.
        url = logout_url
        api.inject_js = False

    logger.info('Opening Epic Games login window...')
    # Open logout URL first to remove existing cookies, then redirect to login.
    window = webview.create_window(f'Legendary {__version__} - Epic Games Account Login',
                                   url=url, width=768, height=1024, js_api=api)
    api.window = window
    window.events.loaded += api.on_loaded

    try:
        webview.start(user_agent=user_agent)
    except Exception as we:
        logger.error(f'Running webview failed with {we!r}. If this error persists try the manual '
                     f'login process by adding --disable-webview to your command line.')
        return None

    if api.callback_result is None:
        logger.error('Login aborted by user.')

    return api.callback_result
