#!/usr/bin/env python2
from __future__ import print_function
import datetime
import logging
import pwd
from optparse import OptionParser
import os
import re
from subprocess import Popen, STDOUT
import sys

from six.moves import configparser


CONFIG_PATH = "/etc/endpoints.ini"
LOG_DIR = "/var/log/update-remote-wn-client"


log = logging.getLogger(__name__)


class Error(Exception):
    pass


def call_updater(cfg, section, updater_script, log_dir, dry_run=False):
    """Call a single instance of the updater for a config section.
    Returns an (endpoint name, subprocess.Popen) tuple.

    """
    m = re.match(r"Endpoint\s+([^\s:\\/]+)\s*$", section)
    if not m:
        raise Error(
            "%s: Endpoint name can't contain whitespace, `\\`, `/`, or `:`" % section
        )
    endpoint = m.group(1)

    def safe_get(option):
        try:
            return cfg.get(section, option)
        except (configparser.NoSectionError, configparser.NoOptionError):
            return

    local_user = safe_get("local_user")
    remote_host = safe_get("remote_host")
    upstream_url = safe_get("upstream_url")
    remote_user = safe_get("remote_user")
    remote_dir = safe_get("remote_dir")

    if not local_user:
        raise Error("%s: Missing required option local_user" % section)
    if not remote_host:
        raise Error("%s: Missing required option remote_host" % section)
    try:
        pwd.getpwnam(local_user)
    except KeyError:
        raise Error("%s: local_user %s does not exist" % (section, local_user))

    cmd = [updater_script, remote_host]
    if upstream_url:
        cmd.append("--upstream-url=%s" % upstream_url)
    if remote_user:
        cmd.append("--remote-user=%s" % remote_user)
    if remote_dir:
        cmd.append("--remote-dir=%s" % remote_dir)

    if dry_run:
        print("Would run %r as %s" % (cmd, local_user))
        return

    log_file_path = os.path.join(log_dir, endpoint)
    with open(log_file_path, "ab") as log_file:
        log_file.write(b"-" * 79 + b"\n")
        log_file.write(
            b"Started at %s\nUser: %s\nCommand: %r\n\n"
            % (datetime.datetime.now(), local_user, cmd)
        )
        proc = Popen(
            ["sudo", "-H", "-u", local_user] + cmd,
            stdout=log_file,
            stderr=STDOUT,
            cwd="/",
        )
        returncode = proc.wait()

    if returncode == 0:
        log.info("Endpoint %s ok.", endpoint)
    else:
        raise Error(
            "Endpoint %s FAILED with error %d.  See %s for details."
            % (endpoint, returncode, log_file_path)
        )


def which(executable):
    for d in os.environ["PATH"].split(":"):
        test_path = os.path.join(d, executable)
        if os.path.exists(test_path) and os.access(test_path, os.X_OK):
            return os.path.abspath(test_path)


def main():
    parser = OptionParser()
    parser.add_option(
        "--config",
        default=CONFIG_PATH,
        help="Location of config file. [default: %default]",
    )
    parser.add_option(
        "--log-dir",
        default=LOG_DIR,
        help="Location of log directory. [default: %default]",
    )
    parser.add_option(
        "-n",
        "--dry-run",
        action="store_true",
        help="Don't change anything, just print the commands that would be run",
    )
    opts, _ = parser.parse_args()

    cfg = configparser.SafeConfigParser()
    cfg.read(opts.config)

    sections_list = list(x for x in cfg.sections() if x.startswith("Endpoint"))
    if not sections_list:
        log.error("No Endpoint sections found")
        return 1

    updater_script = which("update-remote-wn-client")
    if not updater_script:
        msg = "update-remote-wn-client not found in PATH"
        if opts.dry_run:
            log.warning(msg)
            updater_script = "update-remote-wn-client"
        else:
            log.error(msg)
            return 1

    try:
        for section in sections_list:
            call_updater(cfg, section, updater_script, opts.log_dir, opts.dry_run)
    except Error as e:
        log.error(e)
        return 1

    return 0


if __name__ == "__main__":
    logging.basicConfig(format="*** %(message)s", level=logging.INFO)
    sys.exit(main())
