#!/usr/bin/env python
import getopt
import logging
import os
import sys
from typing import List, Optional

import tomodachi
from tomodachi.config import parse_config_files
from tomodachi.launcher import ServiceLauncher
from tomodachi.watcher import Watcher

try:
    if ModuleNotFoundError:
        pass
except Exception:
    class ModuleNotFoundError(ImportError):
        pass


class CLI:
    def help_command_usage(self) -> str:
        return ('Usage: tomodachi.py subcommand [options] [args]\n'
                '\n'
                'Options:\n'
                '  -h, --help             show this help message and exit\n'
                '  -v, --version          print tomodachi version\n'
                '  --dependency-versions  print versions of dependencies\n'
                '\n'
                'Available subcommands:\n'
                '  run <service ...> [-c <config-file ...>] [--production]\n'
                '  -c, --config <files>   use json configuration files\n'
                '  -l, --log <level>      specify log level\n'
                '  --production           disable restart on file changes\n'
                )

    def help_command(self) -> None:
        print(self.help_command_usage())
        sys.exit(2)

    def version_command(self) -> None:
        print('tomodachi/{}'.format(tomodachi.__version__))
        sys.exit(0)

    def dependency_versions_command(self) -> None:
        self.test_dependencies(fail_on_errors=False, output_versions=True)
        sys.exit(0)

    def test_dependencies(self, fail_on_errors: bool = True, output_versions: bool = False) -> None:
        errors = False

        try:
            import aiobotocore
            if output_versions:
                print('aiobotocore/{}'.format(aiobotocore.__version__))
        except ModuleNotFoundError as e:  # pragma: no cover
            errors = True
            print('Dependency failure: aiobotocore failed to load (error: "{}")'.format(str(e)))
        except Exception as e:  # pragma: no cover
            errors = True
            print('Dependency failure: aiobotocore failed to load (error: "{}")'.format(str(e)))
            logging.exception('')
            print('')

        try:
            import botocore
            if output_versions:
                print('botocore/{}'.format(botocore.__version__))
        except ModuleNotFoundError as e:  # pragma: no cover
            errors = True
            print('Dependency failure: botocore failed to load (error: "{}")'.format(str(e)))
        except Exception as e:  # pragma: no cover
            errors = True
            print('Dependency failure: botocore failed to load (error: "{}")'.format(str(e)))
            logging.exception('')
            print('')

        try:
            import aiohttp
            if output_versions:
                print('aiohttp/{}'.format(aiohttp.__version__))
        except ModuleNotFoundError as e:  # pragma: no cover
            errors = True
            print('Dependency failure: aiohttp failed to load (error: "{}")'.format(str(e)))
        except Exception as e:  # pragma: no cover
            errors = True
            print('Dependency failure: aiohttp failed to load (error: "{}")'.format(str(e)))
            logging.exception('')
            print('')

        try:
            import aioamqp
            if output_versions:
                print('aioamqp/{}'.format(aioamqp.__version__))
        except ModuleNotFoundError as e:  # pragma: no cover
            errors = True
            print('Dependency failure: aioamqp failed to load (error: "{}")'.format(str(e)))
        except Exception as e:  # pragma: no cover
            errors = True
            print('Dependency failure: aioamqp failed to load (error: "{}")'.format(str(e)))
            logging.exception('')
            print('')

        try:
            # Optional
            import google.protobuf
            if output_versions:
                protobuf_version = google.protobuf.__version__.decode() if isinstance(google.protobuf.__version__, bytes) else str(google.protobuf.__version__)
                print('protobuf/{}'.format(protobuf_version))
        except ModuleNotFoundError as e:  # pragma: no cover
            pass
        except Exception as e:  # pragma: no cover
            pass

        if not errors:
            try:
                import tomodachi.invoker
                import tomodachi.helpers.logging
                import tomodachi.transport.amqp
                import tomodachi.transport.aws_sns_sqs
                import tomodachi.transport.http
                import tomodachi.transport.schedule
            except Exception as e:  # pragma: no cover
                errors = True
                print('Dependency failure: tomodachi essentials failed to load (error: "{}")'.format(str(e)))
                logging.exception('')
                print('')

        if errors:
            if fail_on_errors:
                logging.getLogger('exception').warning('Unable to initialize dependencies')
                logging.getLogger('exception').warning('Error: See above exceptions and traceback')
                sys.exit(1)
            else:
                print('There were errors - see above for exceptions and traceback')

    def run_command_usage(self) -> str:
        return 'Usage: tomodachi.py run <service ...> [-c <config-file ...>] [--production]'

    def run_command(self, args: List[str]) -> None:
        if len(args) == 0:
            print(self.run_command_usage())
        else:
            configuration = None
            log_level = logging.INFO

            if '-c' in args or '--config' in args:
                index = args.index('-c') if '-c' in args else args.index('--config')
                args.pop(index)

                config_files = []  # type: List[str]
                while len(args) > index and args[index][0] != '-':
                    value = args.pop(index)
                    if value not in config_files:
                        config_files.append(value)

                if not len(config_files):
                    print('Missing config file on command line')
                    sys.exit(2)

                try:
                    configuration = parse_config_files(config_files)
                except FileNotFoundError as e:
                    print('Invalid config file: {}'.format(str(e)))
                    sys.exit(2)
                except ValueError as e:
                    print('Invalid config file, invalid JSON format: {}'.format(str(e)))
                    sys.exit(2)

            if '--production' in args:
                index = args.index('--production')
                args.pop(index)
                watcher = None
            else:
                cwd = os.getcwd()
                root_directories = [os.getcwd()]
                for arg in set(args):
                    root_directories.append(os.path.dirname('{}/{}'.format(os.path.realpath(cwd), arg)))
                watcher = Watcher(root=root_directories, configuration=configuration)

            if '-l' in args or '--log' in args:
                index = args.index('-l') if '-l' in args else args.index('--log')
                args.pop(index)
                if len(args) > index:
                    log_level = getattr(logging, args.pop(index).upper(), None) or log_level

            logging.basicConfig(format='%(asctime)s (%(name)s): %(message)s', level=log_level)
            logging.Formatter(fmt='%(asctime)s.%(msecs).03d', datefmt='%Y-%m-%d %H:%M:%S')

            self.test_dependencies()

            ServiceLauncher.run_until_complete(set(args), configuration, watcher)
        sys.exit(0)

    def main(self, argv: List[str]) -> None:
        try:
            opts, args = getopt.getopt(argv, "hlvV ", ['help', 'log', 'version', 'version', 'dependency-versions'])
        except getopt.GetoptError:
            self.help_command()
        for opt, _ in opts:
            if opt in ['-h', '--help']:
                self.help_command()
            if opt in ['-v', '-V', '--version']:
                self.version_command()
            if opt in ['--dependency-versions']:
                self.dependency_versions_command()
        if len(args):
            if args[0] in ('run', 'start', 'go'):
                self.run_command(args[1:])
        self.help_command()


def cli_entrypoint(argv: Optional[List[str]] = None) -> None:
    if argv is None:
        argv = sys.argv
        if argv[0].endswith('pytest'):
            argv = ['tomodachi']

    CLI().main(argv[1:])