# -*- coding: utf-8 -*-
'''
Support for reboot, shutdown, etc
'''
from __future__ import absolute_import

# Import python libs
import logging
import re
import datetime
import time
try:
    from shlex import quote as _cmd_quote  # pylint: disable=E0611
except ImportError:
    from pipes import quote as _cmd_quote

# Import salt libs
import salt.utils
import salt.utils.locales

# Set up logging
log = logging.getLogger(__name__)

# Define the module's virtual name
__virtualname__ = 'system'


def __virtual__():
    '''
    This only supports Windows
    '''
    if not salt.utils.is_windows():
        return False
    return __virtualname__


def _convert_minutes_seconds(timeout, in_seconds=False):
    '''
    convert timeout to seconds
    '''
    return timeout if in_seconds else timeout*60


def halt(timeout=5, in_seconds=False):
    '''
    Halt a running system

    timeout
        The wait time before the system will be shutdown.

    in_seconds
        Whether to treat timeout as seconds or minutes.

        .. versionadded:: 2015.8.0

    CLI Example:

    .. code-block:: bash

        salt '*' system.halt 5
    '''
    return shutdown(timeout, in_seconds)


def init(runlevel):
    '''
    Change the system runlevel on sysV compatible systems

    CLI Example:

    .. code-block:: bash

        salt '*' system.init 3
    '''
    #cmd = ['init', runlevel]
    #ret = __salt__['cmd.run'](cmd, python_shell=False)
    #return ret

    # TODO: Create a mapping of runlevels to
    #       corresponding Windows actions

    return 'Not implemented on Windows at this time.'


def poweroff(timeout=5, in_seconds=False):
    '''
    Poweroff a running system

    timeout
        The wait time before the system will be shutdown.

    in_seconds
        Whether to treat timeout as seconds or minutes.

        .. versionadded:: 2015.8.0

    CLI Example:

    .. code-block:: bash

        salt '*' system.poweroff 5
    '''
    return shutdown(timeout, in_seconds)


def reboot(timeout=5, in_seconds=False, wait_for_reboot=False):
    '''
    Reboot the system

    timeout
        The wait time before the system will be shutdown.

    in_seconds
        Whether to treat timeout as seconds or minutes.

        .. versionadded:: 2015.8.0

        The amount of seconds to wait before rebooting

    wait_for_reboot

        .. versionadded:: Beryllium

        Sleeps for timeout + 30 seconds after reboot has been initiated.
        This is useful for use in a highstate for example where
        you have many states that could be ran after this one. Which you don't want
        to start until after the restart i.e You could end up with a half finished state.

    CLI Example:

    .. code-block:: bash

        salt '*' system.reboot 5
        salt '*' system.reboot 5 True
    '''
    seconds = _convert_minutes_seconds(timeout, in_seconds)
    cmd = ['shutdown', '/r', '/t', '{0}'.format(seconds)]
    ret = __salt__['cmd.run'](cmd, python_shell=False)

    if wait_for_reboot:
        time.sleep(seconds + 30)

    return ret


def shutdown(timeout=5, in_seconds=False):
    '''
    Shutdown a running system

    timeout
        The wait time before the system will be shutdown.

    in_seconds
        Whether to treat timeout as seconds or minutes.

        .. versionadded:: 2015.8.0

    CLI Example:

    .. code-block:: bash

        salt '*' system.shutdown 5
    '''
    seconds = _convert_minutes_seconds(timeout, in_seconds)
    cmd = ['shutdown', '/s', '/t', '{0}'.format(seconds)]
    ret = __salt__['cmd.run'](cmd, python_shell=False)
    return ret


def shutdown_hard():
    '''
    Shutdown a running system with no timeout or warning

    CLI Example:

    .. code-block:: bash

        salt '*' system.shutdown_hard
    '''
    cmd = ['shutdown', '/p', '/f']
    ret = __salt__['cmd.run'](cmd, python_shell=False)
    return ret


def set_computer_name(name):
    '''
    Set the Windows computer name

    CLI Example:

    .. code-block:: bash

        salt 'minion-id' system.set_computer_name 'DavesComputer'
    '''
    cmd = ('wmic computersystem where name="%COMPUTERNAME%"'
           ' call rename name="{0}"'.format(name))
    log.debug('Attempting to change computer name. Cmd is: {0}'.format(cmd))
    ret = __salt__['cmd.run'](cmd, python_shell=True)
    if 'ReturnValue = 0;' in ret:
        ret = {'Computer Name': {'Current': get_computer_name()}}
        pending = get_pending_computer_name()
        if pending not in (None, False):
            ret['Computer Name']['Pending'] = pending
        return ret
    return False


def get_pending_computer_name():
    '''
    Get a pending computer name. If the computer name has been changed, and the
    change is pending a system reboot, this function will return the pending
    computer name. Otherwise, ``None`` will be returned. If there was an error
    retrieving the pending computer name, ``False`` will be returned, and an
    error message will be logged to the minion log.

    CLI Example:

    .. code-block:: bash

        salt 'minion-id' system.get_pending_computer_name
    '''
    current = get_computer_name()
    cmd = ['reg', 'query',
           'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\ComputerName\\ComputerName',
           '/v', 'ComputerName']
    output = __salt__['cmd.run'](cmd, python_shell=False)
    pending = None
    for line in output.splitlines():
        try:
            pending = re.search(
                r'ComputerName\s+REG_SZ\s+(\S+)',
                line
            ).group(1)
            break
        except AttributeError:
            continue

    if pending is not None:
        return pending if pending != current else None

    log.error('Unable to retrieve pending computer name using the '
              'following command: {0}'.format(cmd))
    return False


def get_computer_name():
    '''
    Get the Windows computer name

    CLI Example:

    .. code-block:: bash

        salt 'minion-id' system.get_computer_name
    '''
    cmd = ['net', 'config', 'server']
    lines = __salt__['cmd.run'](cmd, python_shell=False).splitlines()
    for line in lines:
        if 'Server Name' in line:
            _, srv_name = line.split('Server Name', 1)
            return srv_name.strip().lstrip('\\')
    return False


def set_computer_desc(desc):
    '''
    Set the Windows computer description

    CLI Example:

    .. code-block:: bash

        salt 'minion-id' system.set_computer_desc 'This computer belongs to Dave!'
    '''
    cmd = ['net',
           'config',
           'server',
           u'/srvcomment:{0}'.format(salt.utils.locales.sdecode(desc))]
    __salt__['cmd.run'](cmd, python_shell=False)
    return {'Computer Description': get_computer_desc()}

set_computer_description = set_computer_desc


def get_computer_desc():
    '''
    Get the Windows computer description

    CLI Example:

    .. code-block:: bash

        salt 'minion-id' system.get_computer_desc
    '''
    cmd = ['net', 'config', 'server']
    lines = __salt__['cmd.run'](cmd, python_shell=False).splitlines()
    for line in lines:
        if 'Server Comment' in line:
            _, desc = line.split('Server Comment', 1)
            return desc.strip()
    return False

get_computer_description = get_computer_desc


def join_domain(
        domain=None,
        username=None,
        password=None,
        account_ou=None,
        account_exists=False
    ):
    '''
    Join a computer to an Active Directory domain

    domain
        The domain to which the computer should be joined, e.g.
        ``my-company.com``

    username
        Username of an account which is authorized to join computers to the
        specified domain. Need to be either fully qualified like
        ``user@domain.tld`` or simply ``user``

    password
        Password of the specified user

    account_ou : None
        The DN of the OU below which the account for this computer should be
        created when joining the domain, e.g.
        ``ou=computers,ou=departm_432,dc=my-company,dc=com``

    account_exists : False
        Needs to be set to ``True`` to allow re-using an existing account

    CLI Example:

    .. code-block:: bash

        salt 'minion-id' system.join_domain domain='domain.tld' \\
                         username='joinuser' password='joinpassword' \\
                         account_ou='ou=clients,ou=org,dc=domain,dc=tld' \\
                         account_exists=False
    '''

    if '@' not in username:
        username = '{0}@{1}'.format(username, domain)

    # remove any escape characters
    if isinstance(account_ou, str):
        account_ou = account_ou.split('\\')
        account_ou = ''.join(account_ou)

    join_options = 3
    if account_exists:
        join_options = 1
    cmd = ('wmic /interactive:off ComputerSystem Where '
           'name="%computername%" call JoinDomainOrWorkgroup FJoinOptions={0} '
           'Name={1} UserName={2} Password="{3}"'
           ).format(
               join_options,
               _cmd_quote(domain),
               _cmd_quote(username),
               password)
    if account_ou:
        # contrary to RFC#2253, 2.1, 'wmic' requires a ; as a RDN separator
        # for the DN
        account_ou = account_ou.replace(',', ';')
        add_ou = ' AccountOU="{0}"'.format(account_ou)
        cmd = cmd + add_ou

    ret = __salt__['cmd.run'](cmd, python_shell=True)
    if 'ReturnValue = 0;' in ret:
        return {'Domain': domain}
    return_values = {
        2:    'Invalid OU or specifying OU is not supported',
        5:    'Access is denied',
        87:   'The parameter is incorrect',
        110:  'The system cannot open the specified object',
        1323: 'Unable to update the password',
        1326: 'Logon failure: unknown username or bad password',
        1355: 'The specified domain either does not exist or could not be contacted',
        2224: 'The account already exists',
        2691: 'The machine is already joined to the domain',
        2692: 'The machine is not currently joined to a domain',
    }
    for value in return_values:
        if 'ReturnValue = {0};'.format(value) in ret:
            log.error(return_values[value])
    return False


def _validate_datetime(newdatetime, valid_formats):
    '''
    Validate `newdatetime` against list of date/time formats understood by
    windows.
    '''
    for dt_format in valid_formats:
        try:
            datetime.datetime.strptime(newdatetime, dt_format)
            return True
        except ValueError:
            continue
    return False


def _validate_time(newtime):
    '''
    Validate `newtime` against list of time formats understood by windows.
    '''
    valid_time_formats = [
        '%I:%M:%S %p',
        '%I:%M %p',
        '%H:%M:%S',
        '%H:%M'
    ]
    return _validate_datetime(newtime, valid_time_formats)


def _validate_date(newdate):
    '''
    Validate `newdate` against list of date formats understood by windows.
    '''
    valid_date_formats = [
        '%Y-%m-%d',
        '%m/%d/%y',
        '%y/%m/%d'
    ]
    return _validate_datetime(newdate, valid_date_formats)


def get_system_time():
    '''
    Get the Windows system time

    CLI Example:

    .. code-block:: bash

        salt '*' system.get_system_time
    '''
    cmd = 'time /T'
    return __salt__['cmd.run'](cmd, python_shell=True)


def set_system_time(newtime):
    '''
    Set the Windows system time

    CLI Example:

    .. code-block:: bash

        salt '*' system.set_system_time '11:31:15 AM'
    '''
    if not _validate_time(newtime):
        return False
    cmd = 'time {0}'.format(newtime)
    return not __salt__['cmd.retcode'](cmd, python_shell=True)


def get_system_date():
    '''
    Get the Windows system date

    CLI Example:

    .. code-block:: bash

        salt '*' system.get_system_date
    '''
    cmd = 'date /T'
    return __salt__['cmd.run'](cmd, python_shell=True)


def set_system_date(newdate):
    '''
    Set the Windows system date. Use <mm-dd-yy> format for the date.

    CLI Example:

    .. code-block:: bash

        salt '*' system.set_system_date '03-28-13'
    '''
    if not _validate_date(newdate):
        return False
    cmd = 'date {0}'.format(newdate)
    return not __salt__['cmd.retcode'](cmd, python_shell=True)


def start_time_service():
    '''
    Start the Windows time service

    CLI Example:

    .. code-block:: bash

        salt '*' system.start_time_service
    '''
    return __salt__['service.start']('w32time')


def stop_time_service():
    '''
    Stop the Windows time service

    CLI Example:

    .. code-block:: bash

        salt '*' system.stop_time_service
    '''
    return __salt__['service.stop']('w32time')
