#!/usr/bin/python3

import argparse

from dreamutils.file import recursive_directory_data
import os
import logging
import filetype
from typing import Dict, Union, Text
from json import dump
from ..database import Type
from zipfile import ZipFile, ZIP_DEFLATED


# creating the logger
logger = logging.getLogger('athnos cli')
# the logger format
formatter = logging.Formatter('%(levelname)s: %(message)s')
# creates and configures the loggers console output handler
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
# adding the console handler to the logger
logger.addHandler(console_handler)
# the logger level
logger.setLevel(logging.INFO)


class _AthnosHelpFormatter(argparse.RawDescriptionHelpFormatter):
    """A better format / formatter for the help message"""

    def __init__(self, prog: Text):
        super().__init__(prog)

    def _format_action_invocation(self, action):
        # see https://stackoverflow.com/questions/23936145/python-argparse-help-message-disable-metavar-for-short-options
        if not action.option_strings:
            metavar, = self._metavar_formatter(action, action.dest)(1)
            return metavar
        else:
            parts = []
            # if the Optional doesn't take a value, format is:
            #    -s, --long
            if action.nargs == 0:
                parts.extend(action.option_strings)

            # if the Optional takes a value, format is:
            #    -s ARGS, --long ARGS
            # change to
            #    -s, --long ARGS
            else:
                default = action.dest.upper()
                args_string = self._format_args(action, default)
                for option_string in action.option_strings:
                    # parts.append('%s %s' % (option_string, args_string))
                    parts.append('%s' % option_string)
                parts[-1] += ' %s' % args_string
            return ', '.join(parts)

    def _format_action(self, action: argparse.Action):
        # sets the position of the strings in the help message
        help_position = min(self._action_max_length + 2, self._max_help_position)
        self._max_help_position = self._width - help_position
        return super()._format_action(action)

    def _format_usage(self, usage, actions, groups, prefix=None):
        # this is the help message. maybe there is a better way to do this, but I haven't found it (yet)
        return f'Usage: {self._prog} [-h] [OPTIONS] path\n\n'


def main():
    parser = argparse.ArgumentParser(description='Creates a \'.json\' file, containing all files from a given path '
                                                 'to simplify importing a huge number of files', formatter_class=_AthnosHelpFormatter)

    option = parser.add_argument_group('OPTIONS')
    option.add_argument('-d', '--description', action='store', default='',
                        help='The description for every clip')
    option.add_argument('-o', '--out', action='store', default='clips.json',
                        help='The file to write all information in. Default is \'clips.json\'')
    option.add_argument('-t', '--type', type=str, choices=['audio', 'video', 'image', 'other', 'ignore'], default='ignore',
                        help='Normally the command execution would stop and display an error if the file type can\'t figured out (ending is not specified or not supported) '
                             'but if this flag is specified all files which can\'t be assigned will be from the given type. '
                             'If the value is `ignore`, all not specified files will be ignored')
    option.add_argument('-b', '--build', action='store_true',
                        help='If given a \'clips.zip\' will be created, containing all from the given path. '
                             'Be careful this flag, it might cause a very huge file!')
    option.add_argument('-r', '--recursive', action='store_true',
                        help='If given and the given path is a directory, all files will be selected recursively')
    option.add_argument('-q', '--quiet', action='store_true',
                        help='No output message besides errors will be displayed')
    option.add_argument('-v', '--verbose', action='store_true',
                        help='Shows even more infos')
    option.add_argument('--overwrite', action='store_true',
                        help='If given and the output file (default \'clips.json\' or specified with the `-o` flag) exists, it will be overwritten')
    # parser.add_argument('-src', '--source', action='store_const')
    parser.add_argument('path', action='store', help='Path of the directory to add the files')

    parsed = parser.parse_args()

    clips = {'move': False, 'copy': False, 'root_dir': os.path.realpath(parsed.path), 'clips': []}

    if parsed.quiet:
        logger.setLevel(logging.CRITICAL)
    if parsed.verbose:
        if logger.level == logging.CRITICAL:
            logger.critical('Cannot use \'--quiet\' and \'--verbose\' at the same time')
            return
        else:
            logger.setLevel(logging.DEBUG)

    if parsed.build:
        archive = ZipFile('clips.zip', 'w', compression=ZIP_DEFLATED)
        logger.debug('Building zipfile is enabled')
    else:
        archive = None

    if os.path.exists(parsed.path):
        # if the file from the '--out' flag already exists in this directory and if the '--overwrite' flag is not given
        if os.path.isfile(parsed.out) and not parsed.overwrite:
            logger.critical(f'Cannot write the clips into \'{parsed.out}\': The file already exists. Use the `--overwrite` flag to overwrite the file')
            exit(1)

        files_to_skip = [os.path.realpath(parsed.out)]

        # set the default audio type
        default_clip_type = Type.Clip.from_name(parsed.type)
        if not default_clip_type:
            default_clip_type = Type.Clip.OTHER
        logger.debug(f'The default clip type is \'{parsed.type}\'')

        # get the real / full path of the given path
        full_path = os.path.realpath(parsed.path)
        logger.debug(f'Real path: {full_path}')
        if os.path.isfile(full_path):
            logger.debug('Path is a file')
            # creating a athnos json object out of the file(path)
            clip = _json_object(full_path, parsed.description, default_clip_type)
            # if the file cannot be ready or something like this
            if clip == OSError:
                logger.critical(f'Cannot process the file: {clip.strerror}')
                return
            # appending the new clip json object to `clips`
            else:
                clips['clips'].append(clip)
        elif os.path.isdir(full_path):
            logger.debug('Path is a directory')
            if parsed.recursive:
                logger.debug('Getting files recursively')
                files = recursive_directory_data(full_path, only_files=True)
            else:
                logger.debug('Getting directory files')
                files = [os.path.join(full_path, file) for file in os.listdir(full_path) if os.path.isfile(file)]

            for file in files:
                # checks first if the file in `files_to_skip`
                if file in files_to_skip:
                    logger.warning(f'Skipping file {file}')
                else:
                    # creating a athnos json object out of the file(path)
                    clip = _json_object(file, parsed.description, default_clip_type)
                    if clip != OSError:
                        logger.debug(f'Adding file {file}')
                        clips['clips'].append(clip)
                        # if `archive` is True and the file is not 'clips.zip', it will be written in the clips.zip file.
                        # if clips.zip tried to write itself into itself, a infinite recursion would be created.
                        # i recognized that, when the file already was around 26GB big...
                        if archive and file != os.path.realpath('clips.zip'):
                            logger.info(f'Adding {file} to archive')
                            try:
                                archive.write(file, arcname=f'clips/{file[len(full_path):]}')
                            except KeyboardInterrupt:
                                logger.info('Aborting process, removing clips.zip...')
                                archive.close()
                                os.remove('clips.zip')
                                logger.debug('Removed clips.zip')
                                exit(1)
    else:
        logger.critical('Given path does not exist')
        return

    if not clips:
        logger.critical('Could not proceed any file')
        if archive:
            logger.critical('Archive has no file, removing clips.zip...')
            archive.close()
            os.remove('clips.zip')
            logger.debug('Removed clips.zip')
        exit(1)
    else:
        # dumping the the clips json object into a file
        dump(clips, open(parsed.out, 'w+'), indent=4)
        logger.debug(f'Dumped all clips into \'{parsed.out}\'')

    if archive:
        archive.close()
        logger.info('Wrote archive')


def _json_object(file: str, description: str, default: Type.Clip = None) -> Union[Dict[str, Union[str, int, Dict[str, Union[str, int]]]], OSError, None]:
    try:
        if filetype.is_audio(file):
            type_ = Type.Clip.AUDIO
        elif filetype.is_video(file):
            type_ = Type.Clip.VIDEO
        elif filetype.is_image(file):
            type_ = Type.Clip.IMAGE
        elif default is not None:
            type_ = default
        else:
            logger.critical(f'{file} is not a supported file type (audio, video, image). '
                            f'Maybe the file has an incorrect file ending?\n'
                            f'See https://github.com/h2non/filetype.py#supported-types for all supported file endings (only audio, video and image) '
                            f'or use the `-t` flag to specify what should happen with not correctly recognized files (use `-h` to get help)\n')
            exit(0)
            return

        return {'source': {'type': '', 'path': '', 'name': '', 'description': '', 'season': '', 'episode': ''},
                'path': file,
                'type': type_.value,
                'name': os.path.splitext(os.path.basename(file))[0],
                'description': description}
    # this gets caused if, for example, the given file is a socket file in linux
    except OSError as e:
        return e
