import sys
import traceback
import os
import json
import time
import webbrowser
import urllib.parse
from datetime import date
import logging
import logging.handlers
from pathlib import Path
from typing import Union
from functools import wraps, cached_property
from tempfile import gettempdir

from .launcher import Launcher
from .browser import Browser
from .settings import Settings

PLUGIN_MANIFEST = 'plugin.json'
FLOW_LAUNCHER_DIR_NAME = "FlowLauncher"
SCOOP_FLOW_LAUNCHER_DIR_NAME = "flow-launcher"
WOX_DIR_NAME = "Wox"
FLOW_API = 'Flow.Launcher'
WOX_API = 'Wox'
APP_DIR = None
USER_DIR = None
LOCALAPPDATA = Path(os.getenv('LOCALAPPDATA'))
APPDATA = Path(os.getenv('APPDATA'))
FILE_PATH = os.path.dirname(os.path.abspath(__file__))
CURRENT_WORKING_DIR = Path().cwd()
LAUNCHER_NOT_FOUND_MSG = f"Unable to locate Launcher directory\nCurrent working directory: {CURRENT_WORKING_DIR}"


launcher_dir = None
path = CURRENT_WORKING_DIR
if SCOOP_FLOW_LAUNCHER_DIR_NAME.lower() in str(path).lower():
    launcher_name = SCOOP_FLOW_LAUNCHER_DIR_NAME
    API = FLOW_API
elif FLOW_LAUNCHER_DIR_NAME.lower() in str(path).lower():
    launcher_name = FLOW_LAUNCHER_DIR_NAME
    API = FLOW_API
elif WOX_DIR_NAME.lower() in str(path).lower():
    launcher_name = WOX_DIR_NAME
    API = WOX_API
else:
    raise FileNotFoundError(LAUNCHER_NOT_FOUND_MSG)

while True:
    if len(path.parts) == 1:
        raise FileNotFoundError(LAUNCHER_NOT_FOUND_MSG)
    if path.joinpath('Settings').exists():
        USER_DIR = path
        if USER_DIR.name == 'UserData':
            APP_DIR = USER_DIR.parent
        elif str(CURRENT_WORKING_DIR).startswith(str(APPDATA)):
            APP_DIR = LOCALAPPDATA.joinpath(launcher_name)
        else:
            raise FileNotFoundError(LAUNCHER_NOT_FOUND_MSG)
        break

    path = path.parent

APP_ICONS = APP_DIR.joinpath("Images")
ICON_APP = APP_DIR.joinpath('app.png')
ICON_APP_ERROR = APP_DIR.joinpath(APP_ICONS, 'app_error.png')
ICON_BROWSER = APP_DIR.joinpath(APP_ICONS, 'browser.png')
ICON_CALCULATOR = APP_DIR.joinpath(APP_ICONS, 'calculator.png')
ICON_CANCEL = APP_DIR.joinpath(APP_ICONS, 'cancel.png')
ICON_CLOSE = APP_DIR.joinpath(APP_ICONS, 'close.png')
ICON_CMD = APP_DIR.joinpath(APP_ICONS, 'cmd.png')
ICON_COLOR = APP_DIR.joinpath('color.png')
ICON_CONTROL_PANEL = APP_DIR.joinpath('ControlPanel.png')
ICON_COPY = APP_DIR.joinpath('copy.png')
ICON_DELETE_FILE_FOLDER = APP_DIR.joinpath('deletefilefolder.png')
ICON_DISABLE = APP_DIR.joinpath('disable.png')
ICON_DOWN = APP_DIR.joinpath('down.png')
ICON_EXE = APP_DIR.joinpath('exe.png')
ICON_FILE = APP_DIR.joinpath('file.png')
ICON_FIND = APP_DIR.joinpath('find.png')
ICON_FOLDER = APP_DIR.joinpath('folder.png')
ICON_HISTORY = APP_DIR.joinpath('history.png')
ICON_IMAGE = APP_DIR.joinpath('image.png')
ICON_LOCK = APP_DIR.joinpath('lock.png')
ICON_LOGOFF = APP_DIR.joinpath('logoff.png')
ICON_OK = APP_DIR.joinpath('ok.png')
ICON_OPEN = APP_DIR.joinpath('open.png')
ICON_PICTURES = APP_DIR.joinpath('pictures.png')
ICON_PLUGIN = APP_DIR.joinpath('plugin.png')
ICON_PROGRAM = APP_DIR.joinpath('program.png')
ICON_RECYCLEBIN = APP_DIR.joinpath('recyclebin.png')
ICON_RESTART = APP_DIR.joinpath('restart.png')
ICON_SEARCH = APP_DIR.joinpath('search.png')
ICON_SETTINGS = APP_DIR.joinpath('settings.png')
ICON_SHELL = APP_DIR.joinpath('shell.png')
ICON_SHUTDOWN = APP_DIR.joinpath('shutdown.png')
ICON_SLEEP = APP_DIR.joinpath('sleep.png')
ICON_UP = APP_DIR.joinpath('up.png')
ICON_UPDATE = APP_DIR.joinpath('update.png')
ICON_URL = APP_DIR.joinpath('url.png')
ICON_USER = APP_DIR.joinpath('user.png')
ICON_WARNING = APP_DIR.joinpath('warning.png')
ICON_WEB_SEARCH = APP_DIR.joinpath('web_search.png')
ICON_WORK = APP_DIR.joinpath('work.png')


class Flox(Launcher):

    def __init_subclass__(cls, api=API, app_dir=APP_DIR, user_dir=USER_DIR):
        cls._debug = False
        cls.appdir = APP_DIR
        cls.user_dir = USER_DIR
        cls.api = api
        cls._start = time.time()
        cls._results = []
        cls._settings = None
        cls.font_family = '/Resources/#Segoe Fluent Icons'
        cls.issue_item_title = 'Report Issue'
        cls.issue_item_subtitle = 'Report this issue to the developer'

    @cached_property
    def browser(self):
        return Browser(self.app_settings)

    def exception(self, exception):
        self.exception_item(exception)
        self.issue_item(exception)

    def _query(self, query):
        self.args = query.lower()

        self.query(query)

    def _context_menu(self, data):
        self.context_menu(data)

    def exception_item(self, exception):
        self.add_item(
            title=exception.__class__.__name__,
            subtitle=str(exception),
            icon=ICON_APP_ERROR,
            method=self.change_query,
            dont_hide=True
        )

    def issue_item(self, e):
        trace = ''.join(traceback.format_exception(type(e), value=e, tb=e.__traceback__)).replace('\n', '%0A')
        self.add_item(
            title=self.issue_item_title,
            subtitle=self.issue_item_subtitle,
            icon=ICON_BROWSER,
            method=self.create_github_issue,
            parameters=[e.__class__.__name__, trace],
        )

    def create_github_issue(self, title, trace, log=None):
        url = self.manifest['Website']
        if 'github' in url.lower():
            issue_body = f"Please+type+any+relevant+information+here%0A%0A%0A%0A%0A%0A%3Cdetails open%3E%3Csummary%3ETrace+Log%3C%2Fsummary%3E%0A%3Cp%3E%0A%0A%60%60%60%0A{trace}%0A%60%60%60%0A%3C%2Fp%3E%0A%3C%2Fdetails%3E"
            url = f"{url}/issues/new?title={title}&body={issue_body}"
        webbrowser.open(url)

    def add_item(self, title:str, subtitle:str='', icon:str=None, method:Union[str, callable]=None, parameters:list=None, context:list=None, glyph:str=None, score:int=0, **kwargs):
        icon = icon or self.icon
        if not Path(icon).is_absolute():
            icon = Path(self.plugindir, icon)
        item = {
            "Title": str(title),
            "SubTitle": str(subtitle),
            "IcoPath": str(icon),
            "ContextData": context,
            "Score": score,
            "JsonRPCAction": {}
        }
        auto_complete_text = kwargs.pop("auto_complete_text", None)

        item["AutoCompleteText"] = auto_complete_text or f'{self.user_keyword} {title}'.replace('* ', '')
        if method:
            item['JsonRPCAction']['method'] = getattr(method, "__name__", method)
            item['JsonRPCAction']['parameters'] = parameters or []
            item['JsonRPCAction']['dontHideAfterAction'] = kwargs.pop("dont_hide", False)
        if glyph:
            item['Glyph'] = {}
            item['Glyph']['Glyph'] = glyph
            font_family =  kwargs.pop("font_family", self.font_family)
            if font_family.startswith("#"):
                font_family = str(Path(self.plugindir).joinpath(font_family))
            item['Glyph']['FontFamily'] = font_family
        for kw in kwargs:
            item[kw] = kwargs[kw]
        self._results.append(item)
        return self._results[-1]

    @cached_property
    def plugindir(self):
        potential_paths = [
            os.path.abspath(os.getcwd()),
            os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
        ]

        for path in potential_paths:

            while True:
                if os.path.exists(os.path.join(path, PLUGIN_MANIFEST)):
                    return path
                elif os.path.ismount(path):
                    return os.getcwd()

                path = os.path.dirname(path)

    @cached_property
    def manifest(self):
        with open(os.path.join(self.plugindir, PLUGIN_MANIFEST), 'r', encoding='utf-8') as f:
            return json.load(f)

    @cached_property
    def id(self):
        return self.manifest['ID']

    @cached_property
    def icon(self):
        return self.manifest['IcoPath']

    @cached_property
    def action_keyword(self):
        return self.manifest['ActionKeyword']

    @cached_property
    def version(self):
        return self.manifest['Version']

    @cached_property
    def appdata(self):
        # Userdata should be up two directories from plugin root
        return os.path.dirname(os.path.dirname(self.plugindir))

    @property
    def app_settings(self):
        with open(os.path.join(self.appdata, 'Settings', 'Settings.json'), 'r', encoding='utf-8') as f:
            return json.load(f)

    @property
    def query_search_precision(self):
        return self.app_settings.get('QuerySearchPrecision', 'Regular')

    @cached_property
    def user_keywords(self):
        return self.app_settings['PluginSettings']['Plugins'].get(self.id, {}).get('UserKeywords', [self.action_keyword])

    @cached_property
    def user_keyword(self):
        return self.user_keywords[0]

    @cached_property
    def appicon(self, icon):
        return os.path.join(self.appdir, 'images', icon + '.png')

    @property
    def applog(self):
        today = date.today().strftime('%Y-%m-%d')
        file = f"{today}.txt"
        return os.path.join(self.appdata, 'Logs', self.appversion, file)

    
    @cached_property
    def appversion(self):
        return os.path.basename(self.appdir).replace('app-', '')

    @cached_property
    def logfile(self):
        file = "plugin.log"
        return os.path.join(self.plugindir, file)

    @cached_property
    def logger(self):
        logger = logging.getLogger('')
        formatter = logging.Formatter(
            '%(asctime)s %(levelname)s (%(filename)s): %(message)s',
            datefmt='%H:%M:%S')
        logfile = logging.handlers.RotatingFileHandler(
                self.logfile,
                maxBytes=1024 * 2024,
                backupCount=1)
        logfile.setFormatter(formatter)
        logger.addHandler(logfile)
        logger.setLevel(logging.WARNING)
        return logger

    def logger_level(self, level):
        if level == "info":
            self.logger.setLevel(logging.INFO)
        elif level == "debug":
            self.logger.setLevel(logging.DEBUG)
        elif level == "warning":
            self.logger.setLevel(logging.WARNING)
        elif level == "error":
            self.logger.setLevel(logging.ERROR)
        elif level == "critical":
            self.logger.setLevel(logging.CRITICAL)

    @cached_property
    def api(self):
        launcher = os.path.basename(os.path.dirname(self.appdir))
        if launcher == 'FlowLauncher':
            return FLOW_API
        else:
            return WOX_API

    @cached_property
    def name(self):
        return self.manifest['Name']

    @cached_property
    def author(self):
        return self.manifest['Author']

    @cached_property
    def settings_path(self):
        dirname = self.name
        setting_file = "Settings.json"
        return os.path.join(self.appdata, 'Settings', 'Plugins', dirname, setting_file)

    @cached_property
    def settings(self):
        if not os.path.exists(os.path.dirname(self.settings_path)):
            os.mkdir(os.path.dirname(self.settings_path))
        return Settings(self.settings_path)

    def browser_open(self, url):
        self.browser.open(url)

    @cached_property
    def python_dir(self):
        return self.app_settings["PluginSettings"]["PythonDirectory"]

    def log(self):
        return self.logger