#!/usr/bin/env python
# -*- coding: utf-8 -*-
##
#
# Vaisala software source code file
#
# Copyright (c) Vaisala Oyj 2015-2016. All rights reserved.
#
##
"""Command line tool for generating passwords for various services which are
configured in INI files.

"""
from __future__ import print_function

import argparse
import codecs
import ConfigParser
import datetime
import filecmp
import grp
import logging
import os
import pwd
import shutil
import string
import sys

from os import environ as env
from os import path


logger = logging.getLogger('rsw-ini-password-configure')


def random_password(length=32):
    chars = string.ascii_uppercase + string.digits + string.ascii_lowercase
    password = ''
    for i in range(length):
        password += chars[ord(os.urandom(1)) % len(chars)]
    return password


def extract_option(file_path, section, option):
    """Extracts the option value from a given ini file and does no error
    handling. User is expected to handle ConfigParser thrown errors.

    See https://docs.python.org/2.7/library/configparser.html#ConfigParser.Error
    for details.
    """
    config = ConfigParser.SafeConfigParser()
    config.read(file_path)
    return config.get(section, option)


def extract_option_from_properties(file_path, option):
    """Extracts the option value from a given properties file and does no error
    handling.
    """
    with open(file_path) as file:
        for line in file:
            if line.startswith(option):
                return line.split('=')[1].strip()


def configure(file_path, section, option, value):
    conf = ConfigParser.RawConfigParser()
    logger.debug(u"Read configuration from {}".format(file_path))
    conf.read(file_path)

    if not conf.has_section(section):
        conf.add_section(section)

    conf.set(section, option, value)
    return conf


def is_properties_file(file_name):
    return args.file.endswith(".properties")


def main(args):
    # File ownership and permissions setup
    file_permissions = int(args.file_permissions, 8)

    file_owner = args.file_owner
    if file_owner is None:
        try:
            file_owner = pwd.getpwuid(os.stat(args.file).st_uid).pw_name
        except:
            sys.exit(u"Cannot determine file owner for '{}' and ".format(args.file) +
                     "it's not passed in via command line.")
    file_owner_uid = pwd.getpwnam(file_owner).pw_uid

    file_group = args.file_group
    if file_group is None:
        try:
            file_group = grp.getgrgid(os.stat(args.file).st_gid).gr_name
        except:
            sys.exit(u"Cannot determine file group for '{}' and ".format(args.file) +
                     "it's not passed in via command line.")
    file_group_gid = grp.getgrnam(file_group).gr_gid

    # File names
    rpmsave_file = args.file + ".rpmsave"
    temp_file = args.file + ".tmp"
    properties_file = is_properties_file(args.file)

    # Configuration logic proper
    current_value = None
    try:
        if properties_file:
            current_value = extract_option_from_properties(args.file, args.option)
        else:
            current_value = extract_option(args.file, args.section, args.option)
    except ConfigParser.Error:
        pass

    if current_value is not None and not args.force:
        logger.info(u"Option '{}' already set.".format(args.option))
        sys.exit(0)

    backup_value = None
    if args.use_rpmsave:
        try:
            if properties_file:
                backup_value = extract_option_from_properties(rpmsave_file, args.option)
            else:
                backup_value = extract_option(rpmsave_file, args.section, args.option)
        except:
            pass

        if isinstance(backup_value, basestring):
            logger.info(u"Option '{}' found from .rpmsave, using that.".format(args.option))

    generated_value = None
    if backup_value is None and args.generate_password:
        logger.info("Creating a new random value for option...")
        generated_value = random_password()

    if backup_value is None and generated_value is None:
        sys.exit("Cannot resolve old value from .rpmsave and not generating a new password.")

    new_value = filter(None, [backup_value, generated_value])[0] # take first non-False value

    if properties_file:
        with open(temp_file, "wb") as tmp_f:
            with open(args.file) as f:
                replaced = False
                for line in f:
                    if line.startswith(args.option):
                        newline = args.option + " = " + new_value
                        tmp_f.write(newline)
                        replaced = True
                    else:
                        tmp_f.write(line)
                if not replaced:
                    newline = args.option + " = " + new_value
                    tmp_f.write(newline)
    else:
        config = configure(args.file, args.section, args.option, new_value)
        with open(temp_file, "wb") as f:
            config.write(f)

    changed = not filecmp.cmp(temp_file, args.file)
    if changed:
        backup_suffix = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
        backup_file = args.file + "." + backup_suffix
        shutil.copyfile(args.file, backup_file)

        try:
            if not args.no_chown:
                os.chown(backup_file, file_owner_uid, file_group_gid)
            os.chmod(backup_file, file_permissions)
        except:
            pass

        if not args.no_chown:
            os.chown(temp_file, file_owner_uid, file_group_gid)
        os.chmod(temp_file, file_permissions)
        os.rename(temp_file, args.file)

    if os.path.isfile(temp_file):
        logger.debug(u"Remove temporary file {}".format(temp_file))
        os.remove(temp_file)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("--file", dest="file", required=True,
                        help="INI file to oprate on.")
    parser.add_argument("-s", "--section", dest="section", required=True,
                        help="INI section the password option is in.")
    parser.add_argument("-o", "--option", dest="option", required=True,
                        help="INI option to set.")

    parser.add_argument("--resolve-from-rpmsave", dest="use_rpmsave",
                        action="store_true", default=False,
                        help="If specified, old option value " +
                        "is restored from file with '.rpmsave' suffix.")

    parser.add_argument("--generate-password", dest="generate_password",
                        action="store_true", default=False)

    parser.add_argument("--force", dest="force", action="store_true",
                        default=False, help="Override current value even if it's set.")

    parser.add_argument("--file-permissions", dest="file_permissions",
                        default="0o600", required=False,
                        help="Permissions for the file.")
    parser.add_argument("--file-owner", dest="file_owner",
                        help="Username for the file, if not set, use file's current one.",
                        default=None, required=False)
    parser.add_argument("--file-group", dest="file_group",
                        help="Group for the file, if not set, use file's current one.",
                        default=None, required=False)

    # Testing argument
    parser.add_argument("--no-chown", dest="no_chown",
                        help=argparse.SUPPRESS, default=False, action="store_true")

    parser.add_argument("-d", "--debug", dest="debug", action="store_true",
                        help="Enable debug messages.")
    args = parser.parse_args()

    def setup_logging(debug_enabled):
        root = logging.getLogger()
        if debug_enabled:
            root.setLevel(logging.DEBUG)
        else:
            root.setLevel(logging.INFO)
        ch = logging.StreamHandler(codecs.getwriter('utf-8')(sys.stdout))
        ch.setLevel(logging.DEBUG)
        formatter = logging.Formatter(u'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        ch.setFormatter(formatter)
        root.addHandler(ch)
    setup_logging(args.debug)

    main(args)
