import os
import sys
import shutil
import logging
import configparser

from collections import defaultdict

from .exceptions import HelpfulError, AliasError

log = logging.getLogger(__name__)

class Alias:
    # noinspection PyUnresolvedReferences
    def __init__(self, bot, alias_file):
        self.alias_file = alias_file
        self.find_alias_file()
        self.bot = bot

        self.aliases = defaultdict(list)

        config = configparser.ConfigParser(interpolation=None)
        config.read(alias_file, encoding='utf-8')

        confsections = {"Aliases"}.difference(config.sections())
        if confsections:
            raise HelpfulError(
                "One or more required alias config sections are missing.",
                "Fix your alias config.  Each [Section] should be on its own line with "
                "nothing else on it.  The following sections are missing: {}".format(
                    ', '.join(['[%s]' % s for s in confsections])
                ),
                preface="An error has occured parsing the alias config:\n"
            )

        for key in config['Aliases']:
            self.aliases[key] = config['Aliases'][key].split()

    def find_alias_file(self):
        config = configparser.ConfigParser(interpolation=None)

        if not os.path.isfile(self.alias_file):
            if os.path.isfile(self.alias_file + '.ini'):
                shutil.move(self.alias_file + '.ini', self.alias_file)
                log.info("Moving {0} to {1}, you should probably turn file extensions on.".format(
                    self.alias_file + '.ini', self.alias_file
                ))

            elif os.path.isfile('config/example_alias.ini'):
                shutil.copy('config/example_alias.ini', self.alias_file)
                log.warning('Aliases file not found, copying example_alias.ini')

            else:
                raise HelpfulError(
                    "Your alias config files are missing. Neither alias.ini nor example_alias.ini were found.",
                    "Grab the files back from the archive or remake them yourself and copy paste the content "
                    "from the repo. Stop removing important files!"
                )

        if not config.read(self.alias_file, encoding='utf-8'):
            c = configparser.ConfigParser()
            try:
                # load the config again and check to see if the user edited that one
                c.read(self.alias_file, encoding='utf-8')

            except AliasError as e:
                print(flush=True)
                log.critical("Unable to copy config/example_alias.ini to {}".format(self.alias_file), exc_info=e)
                sys.exit(2)

    def write_alias(self, location = None):        
        if not location:
            location = self.alias_file

        config = configparser.ConfigParser(interpolation=None, allow_no_value=True)
        config.set(configparser.DEFAULTSECT, '# This is the alias file for adding aliases to commands in the MusicBot.')
        config.set(configparser.DEFAULTSECT, '# Do not edit this file using Notepad as it ruins the formatting - use')
        config.set(configparser.DEFAULTSECT, '# Notepad++ or a code editor like Visual Studio Code.')
        config.add_section('Aliases')
        for key, value in self.aliases.items():
            config.set('Aliases', key, ' '.join(value))
        with open(location, 'w') as f:
            config.write(f)

    def fix_alias(self, cmd, specific_prompt = None):
        cdict = defaultdict(list)
        cdict[None].append(cmd)
        if hasattr(cmd, 'walk_commands'):
            for child in cmd.walk_commands():
                cdict[child.parent].append(child)
                
        for commandlist in cdict.values():
            for command in commandlist:
                cog = command.cog
                if command.qualified_name in self.aliases:
                    self.bot.log.debug('setting aliases for {}{} as {}'.format(
                        command.qualified_name,
                        '' if specific_prompt is None else ' ({})'.format(specific_prompt),
                        self.aliases[command.qualified_name])
                    )
                    command.update(aliases = self.aliases[command.qualified_name].copy())
                else:
                    # @TheerapakG: for simplicity sake just update it so that I don't have to solve the add_command headache
                    command.update()
                command.cog = cog
        for parent, commandlist in cdict.items():
            if parent:
                for command in commandlist:
                    parent.add_command(command)

    def fix_chained_command_alias(self, cmd, specific_prompt = None):
        if cmd.parent:
            parent = cmd.parent
            case = 'w/ parent'
        else:
            parent = self.bot._super
            case = 'w/o parent'
        parent.remove_command(cmd.name)
        self.fix_alias(cmd, '{} {}'.format(specific_prompt, case) if specific_prompt else None)
        parent.add_command(cmd)

    def add_alias(self, command, alias):
        origc = self.bot.get_command(command)
        if origc:
            if alias in self.aliases[origc.name]:
                raise AliasError('command given already have that alias')

            if origc.parent:
                parent = origc.parent
            else:
                parent = self.bot

            ouch = parent.get_command(alias)
            if ouch:
                raise AliasError('already have that alias registered with the command `{}`'.format(ouch.name))

            self.aliases[origc.name].append(alias)
            self.fix_chained_command_alias(origc, 'added alias')
            if self.bot.config.persistent_alias:
                self.write_alias()
        else:
            raise AliasError('no such command')

    def remove_alias(self, alias):
        origc = self.bot.get_command(alias)
        if origc:
            try:
                self.aliases[origc.name].remove(alias.split()[-1])
            except ValueError:
                raise AliasError('alias is a command')
            else:
                self.fix_chained_command_alias(origc, 'removed alias')
                if self.bot.config.persistent_alias:
                    self.write_alias()
        else:
            raise AliasError('no such alias')
            
class AliasDefaults:
    alias_file = 'config/alias.ini' 