#!/usr/bin/python3
#
# Licensed under the GNU General Public License Version 3
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright 2013 Aron Parsons <aronparsons@gmail.com>
#

"""spacecmd - a command line interface to Spacewalk"""
from spacecmd.i18n import _N

import errno
import gettext
import logging
import os
import re
import sys

try:
    from xmlrpc import client as xmlrpclib
except ImportError:
    import xmlrpclib
import codecs
import locale
import argparse

try:
    from configparser import ConfigParser
except ImportError:  # python 2
    from ConfigParser import SafeConfigParser as ConfigParser

import socket

translation = gettext.translation("spacecmd", fallback=True)
try:
    _ = translation.ugettext
except AttributeError:
    _ = translation.gettext

_INTRO = _(
    """Welcome to spacecmd, a command-line interface to Spacewalk.

Type: 'help' for a list of commands
      'help <cmd>' for command-specific help
      'quit' to quit
"""
)

_SYSTEM_CONF_FILE = "/etc/spacecmd.conf"


def get_localhost_fqdn():
    """
    Get FQDN of the current machine.

    :return:
    """
    fqdn = None
    try:
        for family, socktype, proto, canonname, sockaddr in socket.getaddrinfo(
            socket.gethostname(), 0, 0, 0, 0, socket.AI_CANONNAME
        ):
            if canonname:
                fqdn = canonname
                break
    except socket.gaierror as exc:
        logging.debug("Error while getting FQDN over the network: %s", str(exc))
    except Exception as exc:
        logging.error(_N("Unhandled exception occurred while getting FQDN:"), str(exc))

    return fqdn or socket.getfqdn()


def _redirect_stdout_to_devnull():
    # Python flushes standard streams on exit; redirect remaining output
    # to devnull to avoid another BrokenPipeError at shutdown
    devnull = os.open(os.devnull, os.O_WRONLY)
    os.dup2(devnull, sys.stdout.fileno())


if __name__ == "__main__":
    # disable no-member error message
    # pylint: disable=E1101

    try:
        if not sys.stdout.isatty():
            try:  # Python 3
                sys.stdout = codecs.getwriter(locale.getpreferredencoding())(
                    sys.stdout.buffer
                )
            except AttributeError:  # Python 2
                sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout)

        usage = "%(prog)s [options] [command] [-- [command options]]"
        parser = argparse.ArgumentParser(usage=usage)
        parser.add_argument(
            "-c", "--config", help=_("config file to use [default: ~/.spacecmd/config]")
        )
        parser.add_argument(
            "-u", "--username", help=_("use this username to connect to the server")
        )
        parser.add_argument(
            "-p",
            "--password",
            help=_(
                "use this password to connect to the server (insecure). Use config instead or spacecmd will ask."
            ),
        )
        parser.add_argument(
            "-s", "--server", help=_("connect to this server [default: local hostname]")
        )
        parser.add_argument(
            "--nossl", action="store_true", help=_("use HTTP instead of HTTPS")
        )
        parser.add_argument(
            "--nohistory", action="store_true", help=_("do not store command history")
        )
        parser.add_argument(
            "-y", "--yes", action="store_true", help=_("answer yes for all questions")
        )
        parser.add_argument(
            "-q", "--quiet", action="store_true", help=_("print only error messages")
        )
        parser.add_argument(
            "-d",
            "--debug",
            action="count",
            default=0,
            help=_("print debug messages (can be passed multiple times)"),
        )
        parser.add_argument("command", nargs="*", help=argparse.SUPPRESS)

        options = parser.parse_args()
        if options.command:
            args = options.command
        else:
            args = []

        # determine the logging level
        if options.debug:
            level = logging.DEBUG
        elif options.quiet:
            level = logging.ERROR
        else:
            level = logging.INFO

        # configure logging
        logging.basicConfig(level=level, format="%(levelname)s: %(message)s")

        # files are loaded from ~/.spacecmd/
        conf_dir = os.path.expanduser("~/.spacecmd")
        user_conf_file = os.path.join(conf_dir, "config")

        # server-specifics will be loaded from the configuration file later
        config = ConfigParser()

        # prevent readline from outputting escape sequences to non-terminals
        if not sys.stdout.isatty():
            logging.debug("stdout is not a TTY, setting TERM=dumb")
            os.environ["TERM"] = "dumb"

        # import our Cmd subclass after we settle our TERM value
        from spacecmd.shell import SpacewalkShell, UnknownCallException

        # create an instance of the shell
        shell = SpacewalkShell(options, conf_dir, config)

        # set the default server to local hostname
        if shell.options.server:
            shell.config["server"] = shell.options.server
        else:
            shell.config["server"] = "localhost"
            shell.config["nossl"] = True

        # don't automatically create config files passed via --config
        if shell.options.config:
            if not os.path.isfile(shell.options.config):
                logging.error(
                    _N("Config file %s does not exist."), shell.options.config
                )
                sys.exit(1)
        else:
            # create an empty configuration file if one's not present
            if not os.path.isfile(user_conf_file):
                try:
                    # create ~/.spacecmd
                    if not os.path.isdir(conf_dir):
                        logging.debug("Creating %s", conf_dir)
                        os.mkdir(conf_dir, int("0700", 8))

                    # create a template configuration file
                    logging.debug("Creating configuration file: %s", user_conf_file)
                    handle = open(user_conf_file, "w")
                    handle.write("[spacecmd]\n")
                    handle.close()
                except IOError:
                    logging.error(_N("Could not create %s"), user_conf_file)

        # load options from configuration files
        if shell.options.config:
            files_read = config.read(
                [_SYSTEM_CONF_FILE, user_conf_file, shell.options.config]
            )
        else:
            files_read = config.read([_SYSTEM_CONF_FILE, user_conf_file])

        for item in files_read:
            logging.debug("Read configuration from %s", item)

        # load the default configuration section
        shell.load_config_section("spacecmd")

        # run a single command from the command line
        if len(args):
            try:
                # rebuild the command and quote all arguments to be safe
                # except for help command
                command = args[0]

                if command == "help":
                    command = " ".join(args)
                if len(args) > 1:
                    command += " %s" % " ".join(
                        "%s" % s if not True in [c.isspace() for c in s] else "'%s'" % s
                        for s in args[1:]
                    )

                # run the command
                precmd = shell.precmd(command)
                if precmd == "":
                    sys.exit(1)
                result = shell.onecmd(precmd)
                logging.debug("command=%s, return_value=%s", precmd, repr(result))
                if result == 1:
                    sys.exit(result)
            except KeyboardInterrupt:
                print()
                print(_("User Interrupt"))
            except UnknownCallException:
                sys.exit(1)
            except BrokenPipeError:
                # We don't want to log BrokenPipeError, and take an early exit
                sys.exit(1)
            except Exception as detail:
                # get the relevant part of a XML-RPC fault
                if isinstance(detail, xmlrpclib.Fault):
                    detail = detail.faultString

                if shell.options.debug:
                    # print(the traceback when debugging)
                    logging.exception(detail)
                else:
                    logging.error(detail)

                sys.exit(1)
        else:
            if not shell.options.quiet:
                print(_INTRO)

            if not shell.do_login(""):
                sys.exit(1)

            # stay in the interactive shell forever
            while True:
                try:
                    shell.cmdloop()
                except KeyboardInterrupt:
                    print()
                except SystemExit:
                    sys.exit(0)
                except UnknownCallException:
                    pass
                except Exception as detail:
                    # get the relevant part of a XML-RPC fault
                    if isinstance(detail, xmlrpclib.Fault):
                        detail = detail.faultString

                        # the session expired
                        if re.search(_("Could not find session"), detail, re.I):
                            shell.session = ""

                    if shell.options.debug:
                        # print(the traceback when debugging)
                        logging.exception(detail)
                    else:
                        logging.error(detail)
        sys.stdout.flush()
    except SystemExit as exc:
        _redirect_stdout_to_devnull()
        sys.exit(exc.code)
    except (IOError, BrokenPipeError) as exc:
        if exc.errno == errno.EPIPE:
            _redirect_stdout_to_devnull()
        else:
            raise exc
