"""
Completions for the global commands
"""
import logging
import os
from functools import reduce
from pathlib import Path
from typing import List, TYPE_CHECKING

from slixmpp import JID, InvalidJID, Iq

from poezio import common
from poezio import tabs
from poezio import xdg
from poezio.config import config
from poezio.roster import roster

from poezio.core.structs import POSSIBLE_SHOW, Completion

if TYPE_CHECKING:
    from poezio.core.core import Core
    from poezio.windows import Input

log = logging.getLogger(__name__)


class CompletionCore:
    def __init__(self, core: 'Core') -> None:
        self.core = core

    def help(self, the_input: 'Input') -> Completion:
        """Completion for /help."""
        commands = sorted(self.core.commands.keys()) + sorted(
            self.core.tabs.current_tab.commands.keys())
        return Completion(the_input.new_completion, commands, 1, quotify=False)

    def status(self, the_input: 'Input') -> Completion | None:
        """
        Completion of /status
        """
        if the_input.get_argument_position() == 1:
            return Completion(
                the_input.new_completion, [status for status in POSSIBLE_SHOW],
                1,
                ' ',
                quotify=False)
        return None

    def roster_barejids(self, the_input: 'Input') -> Completion:
        """Complete roster bare jids"""
        jids = sorted(
            str(contact.bare_jid) for contact in roster.contacts.values()
            if contact.pending_in
        )
        return Completion(the_input.new_completion, jids, 1, '', quotify=False)

    def remove(self, the_input: 'Input') -> Completion:
        """Completion for /remove"""
        jids = [str(jid) for jid in roster.jids()]
        return Completion(the_input.auto_completion, jids, '', quotify=False)

    def presence(self, the_input: 'Input') -> Completion | None:
        """
        Completion of /presence
        """
        arg = the_input.get_argument_position()
        if arg == 1:
            to_suggest = []
            for bookmark in self.core.bookmarks:
                tab = self.core.tabs.by_name_and_class(bookmark.jid,
                                                       tabs.MucTab)
                if tab is not None and tab.joined:
                    to_suggest.append(str(bookmark.jid))
            return Completion(
                the_input.auto_completion,
                [str(j) for j in roster.jids()] + to_suggest,
                '',
                quotify=True)
        elif arg == 2:
            return Completion(
                the_input.auto_completion,
                [status for status in POSSIBLE_SHOW],
                '',
                quotify=True)
        return None

    def theme(self, the_input: 'Input') -> Completion | None:
        """ Completion for /theme"""
        themes_dir_str = config.getstr('themes_dir')
        themes_dir = Path(themes_dir_str).expanduser(
        ) if themes_dir_str else xdg.DATA_HOME / 'themes'
        try:
            theme_files = [
                name.stem for name in themes_dir.iterdir()
                if name.suffix == '.py' and name.name != '__init__.py'
            ]
        except OSError:
            log.exception('Completion for /theme failed')
            return None
        if 'default' not in theme_files:
            theme_files.append('default')
        return Completion(
            the_input.new_completion, theme_files, 1, '', quotify=False)

    def win(self, the_input: 'Input') -> Completion:
        """Completion for /win"""
        comp_list = []
        for tab in self.core.tabs:
            comp_list.extend(tab.matching_names())
        comp = [i[1] for i in comp_list]
        return Completion(the_input.new_completion, comp, 1, '', quotify=False)

    def join(self, the_input: 'Input') -> Completion | None:
        """
        Completion for /join

        Try to complete the MUC JID:
            if only a resource is provided, complete with the default nick
            if only a server is provided, complete with the rooms from the
                disco#items of that server
            if only a nodepart is provided, complete with the servers of the
                current joined rooms
        """
        n = the_input.get_argument_position(quoted=True)
        args = common.shell_split(the_input.text)
        if n != 1:
            # we are not on the 1st argument of the command line
            return None
        if len(args) == 1:
            args.append('')
        try:
            jid = JID(args[1])
        except InvalidJID:
            jid = JID('')
            if args[1].endswith('@'):
                jid.user = args[1][:-1]

        relevant_rooms = []
        relevant_rooms.extend(sorted(self.core.pending_invites.keys()))
        bookmarks = [(str(elem.jid)
                      if not elem.nick else '%s/%s' % (elem.jid, elem.nick))
                     for elem in self.core.bookmarks]
        to_suggest = []
        for bookmark in bookmarks:
            tab = self.core.tabs.by_name_and_class(bookmark, tabs.MucTab)
            if not tab or (tab and not tab.joined):
                to_suggest.append(bookmark)
        relevant_rooms.extend(sorted(to_suggest))

        if the_input.last_completion:
            return Completion(the_input.new_completion, [], 1, quotify=True)

        if jid.user:
            # we are writing the server: complete the server
            serv_list = []
            for tab in self.core.get_tabs(tabs.MucTab):
                if tab.joined:
                    serv_list.append(
                        '%s@%s' % (jid.user, tab.general_jid.server)
                    )
            serv_list.extend(relevant_rooms)
            return Completion(
                the_input.new_completion, serv_list, 1, quotify=True)
        elif args[1].startswith('/'):
            # we completing only a resource
            return Completion(
                the_input.new_completion, ['/%s' % self.core.own_nick],
                1,
                quotify=True)
        else:
            return Completion(
                the_input.new_completion, relevant_rooms, 1, quotify=True)

    def version(self, the_input: 'Input') -> Completion:
        """Completion for /version"""
        comp: list[str] = reduce(
            lambda x, y: x + [str(i.jid) for i in y],
            (
                roster.contacts[jid].resources for jid in roster.jids()
                if len(roster.contacts[jid])
            ),
            []
        )
        return Completion(
            the_input.new_completion, sorted(comp), 1, quotify=False)

    def list(self, the_input: 'Input') -> Completion | None:
        """Completion for /list"""
        muc_serv_list = []
        for tab in self.core.get_tabs(
                tabs.MucTab):  # TODO, also from an history
            if tab.jid.server not in muc_serv_list:
                muc_serv_list.append(tab.jid.server)
        if muc_serv_list:
            return Completion(
                the_input.new_completion, muc_serv_list, 1, quotify=False)
        return None

    def move_tab(self, the_input: 'Input') -> Completion | None:
        """Completion for /move_tab"""
        n = the_input.get_argument_position(quoted=True)
        if n == 1:
            nodes = [tab.name for tab in self.core.tabs if tab]
            nodes.remove('Roster')
            return Completion(
                the_input.new_completion, nodes, 1, ' ', quotify=True)
        return None

    def runkey(self, the_input: 'Input') -> Completion:
        """
        Completion for /runkey
        """
        list_: list[str] = []
        list_.extend(self.core.key_func.keys())
        list_.extend(self.core.tabs.current_tab.key_func.keys())
        return Completion(the_input.new_completion, list_, 1, quotify=False)

    def bookmark(self, the_input: 'Input') -> Completion | None:
        """Completion for /bookmark"""
        args = common.shell_split(the_input.text)
        n = the_input.get_argument_position(quoted=True)

        if n == 2:
            return Completion(
                the_input.new_completion, ['true', 'false'], 2, quotify=True)
        if n >= 3:
            return None

        if len(args) == 1:
            args.append('')
        try:
            jid = JID(args[1])
        except InvalidJID:
            muc_list = [tab.name for tab in self.core.get_tabs(tabs.MucTab)]
            muc_list.sort()
            muc_list.append('*')
            return Completion(the_input.new_completion, muc_list, 1, quotify=True)
        tab = self.core.tabs.by_name_and_class(jid.bare, tabs.MucTab)
        nicks = [tab.own_nick] if tab else []
        default = os.environ.get('USER') if os.environ.get(
            'USER') else 'poezio'
        nick = config.getstr('default_nick') or ''
        if not nick:
            if default and default not in nicks:
                nicks.append(default)
        else:
            if nick not in nicks:
                nicks.append(nick)
        jids_list = ['%s/%s' % (jid.bare, nick) for nick in nicks]
        return Completion(
            the_input.new_completion, jids_list, 1, quotify=True)

    def remove_bookmark(self, the_input: 'Input') -> Completion:
        """Completion for /remove_bookmark"""
        return Completion(
            the_input.new_completion, [str(bm.jid) for bm in self.core.bookmarks],
            1,
            quotify=False)

    def decline(self, the_input: 'Input') -> Completion | None:
        """Completion for /decline"""
        n = the_input.get_argument_position(quoted=True)
        if n == 1:
            return Completion(
                the_input.auto_completion,
                sorted(self.core.pending_invites.keys()),
                '',
                quotify=True)
        return None

    def bind(self, the_input: 'Input') -> Completion | None:
        n = the_input.get_argument_position()
        if n == 1:
            args = [
                key for key in self.core.key_func if not key.startswith('_')
            ]
        elif n == 2:
            args = [key for key in self.core.key_func]
        else:
            return None

        return Completion(the_input.new_completion, args, n, '', quotify=False)

    def message(self, the_input: 'Input') -> Completion | None:
        """Completion for /message"""
        n = the_input.get_argument_position(quoted=True)
        if n >= 2:
            return None
        online: list[str] = []
        offline: list[str] = []
        for jid in sorted((str(jid) for jid in roster.jids())):
            if len(roster.contacts[jid]) > 0:
                online.append(jid)
            else:
                offline.append(jid)
        return Completion(
            the_input.new_completion, online + offline, 1, '', quotify=True)

    def invite(self, the_input: 'Input') -> Completion | None:
        """Completion for /invite"""
        n = the_input.get_argument_position(quoted=True)
        if n == 1:
            comp: list[str] = reduce(
                lambda x, y: x + [str(i.jid) for i in y],
                (
                    roster.contacts[jid].resources for jid in roster.jids()
                    if len(roster.contacts[jid])
                ),
                [],
            )
            comp = sorted(comp)
            bares = sorted(
                str(roster.contacts[contact].bare_jid)
                for contact in roster.jids()
                if len(roster.contacts[contact])
            )
            off = sorted(str(jid) for jid in roster.jids() if jid not in bares)
            comp = comp + bares + off
            return Completion(the_input.new_completion, comp, n, quotify=True)
        elif n == 2:
            rooms = []
            for tab in self.core.get_tabs(tabs.MucTab):
                if tab.joined:
                    rooms.append(tab.jid.bare)
            rooms.sort()
            return Completion(
                the_input.new_completion, rooms, n, '', quotify=True)
        return None

    def impromptu(self, the_input: 'Input') -> Completion:
        """Completion for /impromptu"""
        n = the_input.get_argument_position(quoted=True)
        onlines = []
        offlines = []
        for barejid in roster.jids():
            if len(roster.contacts[barejid]):
                onlines.append(str(barejid))
            else:
                offlines.append(str(barejid))
        comp = sorted(onlines) + sorted(offlines)
        return Completion(the_input.new_completion, comp, n, quotify=True)

    def last_activity(self, the_input: 'Input') -> Completion | None:
        """
        Completion for /last_activity <jid>
        """
        n = the_input.get_argument_position(quoted=False)
        if n >= 2:
            return None
        comp: list[str] = reduce(
            lambda x, y: x + [str(i.jid) for i in y],
            (
                roster.contacts[jid].resources for jid in roster.jids()
                if len(roster.contacts[jid])
            ),
            [],
        )
        return Completion(
            the_input.new_completion, sorted(comp), 1, '', quotify=False)

    def server_cycle(self, the_input: 'Input') -> Completion:
        """Completion for /server_cycle"""
        serv_list = set()
        for tab in self.core.get_tabs(tabs.MucTab):
            serv_list.add(tab.jid.server)
        return Completion(the_input.new_completion, sorted(serv_list), 1, ' ')

    def set(self, the_input: 'Input') -> Completion | None:
        """Completion for /set"""
        args = common.shell_split(the_input.text)
        n = the_input.get_argument_position(quoted=True)
        if n >= len(args):
            args.append('')
        if n == 1:
            if '|' in args[1]:
                plugin_name, section = args[1].split('|')[:2]
                if plugin_name not in self.core.plugin_manager.plugins:
                    return Completion(
                        the_input.new_completion, [], n, quotify=True)
                plugin = self.core.plugin_manager.plugins[plugin_name]
                if plugin is None:
                    return None
                end_list = [
                    '%s|%s' % (plugin_name, section)
                    for section in plugin.config.sections()
                ]
            else:
                end_set = set(config.options('Poezio'))
                end_set.update(config.default.get('Poezio', {}))
                end_list = list(end_set)
                end_list.sort()
        elif n == 2:
            if '|' in args[1]:
                plugin_name, section = args[1].split('|')[:2]
                if plugin_name not in self.core.plugin_manager.plugins:
                    return Completion(
                        the_input.new_completion, [''], n, quotify=True)
                plugin = self.core.plugin_manager.plugins[plugin_name]
                if plugin is None:
                    return None
                end_set = set(plugin.config.options(section or plugin_name))
                if plugin.config.default:
                    end_set.update(
                        plugin.config.default.get(section or plugin_name, {}))
                end_list = list(end_set)
                end_list.sort()
            elif not config.has_option('Poezio', args[1]):
                if config.has_section(args[1]):
                    end_list = config.options(args[1])
                    end_list.append('')
                else:
                    end_list = []
            else:
                end_list = [str(config.get(args[1], '')), '']
        elif n == 3:
            if '|' in args[1]:
                plugin_name, section = args[1].split('|')[:2]
                if plugin_name not in self.core.plugin_manager.plugins:
                    return Completion(
                        the_input.new_completion, [''], n, quotify=True)
                plugin = self.core.plugin_manager.plugins[plugin_name]
                if plugin is None:
                    return None
                end_list = [
                    str(
                        plugin.config.get(args[2], '', section
                                          or plugin_name)), ''
                ]
            else:
                if not config.has_section(args[1]):
                    end_list = ['']
                else:
                    end_list = [str(config.get(args[2], '', args[1])), '']
        else:
            return None
        return Completion(the_input.new_completion, end_list, n, quotify=True)

    def set_default(self, the_input: 'Input') -> Completion | None:
        """ Completion for /set_default
        """
        args = common.shell_split(the_input.text)
        n = the_input.get_argument_position(quoted=True)
        if n >= len(args):
            args.append('')
        if n == 1 or (n == 2 and config.has_section(args[1])):
            return self.set(the_input)
        return None

    def toggle(self, the_input: 'Input') -> Completion:
        "Completion for /toggle"
        return Completion(
            the_input.new_completion,
            config.options('Poezio'),
            1,
            quotify=False)

    def bookmark_local(self, the_input: 'Input') -> Completion | None:
        """Completion for /bookmark_local"""
        n = the_input.get_argument_position(quoted=True)
        args = common.shell_split(the_input.text)

        if n >= 2:
            return None
        if len(args) == 1:
            args.append('')
        try:
            jid = JID(args[1])
        except InvalidJID:
            muc_list = [tab.name for tab in self.core.get_tabs(tabs.MucTab)]
            muc_list.append('*')
            return Completion(the_input.new_completion, muc_list, 1, quotify=True)
        tab = self.core.tabs.by_name_and_class(jid.bare, tabs.MucTab)
        nicks = [tab.own_nick] if tab else []
        default = os.environ.get('USER') if os.environ.get(
            'USER') else 'poezio'
        nick = config.getstr('default_nick')
        if not nick:
            if default and default not in nicks:
                nicks.append(default)
        else:
            if nick not in nicks:
                nicks.append(nick)
        jids_list = ['%s/%s' % (jid.bare, nick) for nick in nicks]
        return Completion(
            the_input.new_completion, jids_list, 1, quotify=True)

    def block(self, the_input: 'Input') -> Completion | None:
        """
        Completion for /block
        """
        if the_input.get_argument_position() == 1:

            current_tab = self.core.tabs.current_tab
            chattabs = (
                tabs.ConversationTab,
                tabs.StaticConversationTab,
                tabs.DynamicConversationTab,
            )
            tabjid: List[str] = []
            if isinstance(current_tab, chattabs):
                tabjid = [current_tab.jid.bare]

            jids = [str(i) for i in roster.jids()]
            jids += tabjid
            return Completion(
                the_input.new_completion, jids, 1, '', quotify=False)
        return None

    def unblock(self, the_input: 'Input') -> Completion | None:
        """
        Completion for /unblock
        """

        def on_result(iq: Iq) -> Completion | None:
            if iq['type'] == 'error':
                return None
            comp = sorted(str(item['jid']) for item in iq['blocklist']['items'])
            return Completion(the_input.new_completion, comp, 1, quotify=False)

        if the_input.get_argument_position():
            self.core.xmpp.plugin['xep_0191'].get_blocked(callback=on_result)
        return None

    report = block
