# -*- coding: utf-8 -*-
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Module for setting and validating the presto-admin config
"""
import os
import re

from fabric.api import env
from overrides import overrides

import trinoadmin.util.fabricapi as util
from trinoadmin import config
from trinoadmin.util import constants
from trinoadmin.trinoclient import CERTIFICATE_ALIAS
from trinoadmin.util.base_config import BaseConfig, SingleConfigItem
from trinoadmin.util.exception import ConfigurationError
from trinoadmin.util.local_config_util import get_topology_path
from trinoadmin.util.validators import validate_username, validate_port, \
    validate_host

# Created by the presto-server RPM package.
PRESTO_STANDALONE_USER = os.environ.get('PRESTO_USER', constants.BRAND)
PRESTO_STANDALONE_GROUP = os.environ.get('PRESTO_GROUP', constants.BRAND)
PRESTO_STANDALONE_USER_GROUP = "%s:%s" % (PRESTO_STANDALONE_USER,
                                          PRESTO_STANDALONE_GROUP)

# CONFIG KEYS
USERNAME = 'username'
PORT = 'port'
COORDINATOR = 'coordinator'
WORKERS = 'workers'

STANDALONE_CONFIG_LOADED = 'standalone_config_loaded'

PRESTO_ADMIN_PROPERTIES = ['username', 'port', 'coordinator', 'workers',
                           'java_home', CERTIFICATE_ALIAS]

DEFAULT_PROPERTIES = {USERNAME: 'root',
                      PORT: 22,
                      COORDINATOR: 'localhost',
                      WORKERS: ['localhost']}


def validate_coordinator(coordinator):
    validate_host(coordinator)
    return coordinator


def validate_workers_for_prompt(workers):
    return validate_workers(workers.split())


_TOPOLOGY_CONFIG = [
    SingleConfigItem(
        USERNAME, 'Enter user name for SSH connection to all nodes:',
        default=DEFAULT_PROPERTIES[USERNAME], validate=validate_username),
    SingleConfigItem(
        PORT, 'Enter port number for SSH connections to all nodes:',
        default=DEFAULT_PROPERTIES['port'], validate=validate_port),
    SingleConfigItem(
        COORDINATOR,
        'Enter host name or IP address for coordinator node. '
        'Enter an external host name or ip address if this is a multi-node '
        'cluster:',
        default=DEFAULT_PROPERTIES['coordinator'],
        validate=validate_coordinator),
    SingleConfigItem(
        WORKERS,
        'Enter host names or IP addresses for worker nodes separated by spaces:',
        default=' '.join(DEFAULT_PROPERTIES['workers']),
        validate=validate_workers_for_prompt)
]


def validate_java_home(java_home):
    return java_home


def validate(conf):
    for key in conf.keys():
        if key not in PRESTO_ADMIN_PROPERTIES:
            raise ConfigurationError('Invalid property: ' + key)

    try:
        username = conf['username']
    except KeyError:
        pass
    else:
        conf['username'] = validate_username(username)

    try:
        java_home = conf['java_home']
    except KeyError:
        pass
    else:
        conf['java_home'] = validate_java_home(java_home)

    try:
        coordinator = conf['coordinator']
    except KeyError:
        pass
    else:
        conf['coordinator'] = validate_coordinator(coordinator)

    try:
        workers = conf['workers']
    except KeyError:
        pass
    else:
        workers = [h for host in workers for h in _expand_host(host)]
        conf['workers'] = validate_workers(workers)

    try:
        port = conf['port']
    except KeyError:
        pass
    else:
        conf['port'] = validate_port(port)
    return conf


def validate_workers(workers):
    if not isinstance(workers, list):
        raise ConfigurationError('Workers must be of type list.  Found ' +
                                 str(type(workers)) + '.')

    if len(workers) < 1:
        raise ConfigurationError('Must specify at least one worker')

    for worker in workers:
        validate_host(worker)
    return workers


def _expand_host(host):
    match = re.match("(.*)\[(\d{1,})-(\d{1,})\](.*)", host)
    if match is not None and len(match.groups()) == 4:
        prefix, start, end, suffix = match.groups()
        if int(start) > int(end):
            raise ValueError("the range must be in ascending order")
        if len(start) == len(end) and len(start) > 1:
            number_format = "{0:0" + str(len(start)) + "d}"
            host_list = [number_format.format(i) for i in range(int(start), int(end) + 1)]
            return [_format_hostname(prefix, i, suffix) for i in host_list]
        else:
            return [_format_hostname(prefix, i, suffix) for i in range(int(start), int(end) + 1)]
    else:
        return [host]


def _format_hostname(prefix, number, suffix):
    return "{prefix}{num}{suffix}".format(prefix=prefix, num=number, suffix=suffix)


class StandaloneConfig(BaseConfig):
    def __init__(self):
        super(StandaloneConfig, self).__init__(get_topology_path(), _TOPOLOGY_CONFIG)

    @overrides
    def read_conf(self):
        conf = self._get_conf_from_file()
        config.fill_defaults(conf, DEFAULT_PROPERTIES)
        validate(conf)
        return conf

    def _get_conf_from_file(self):
        return config.get_conf_from_json_file(self.config_path)

    @overrides
    def is_config_loaded(self):
        return STANDALONE_CONFIG_LOADED in env and env[STANDALONE_CONFIG_LOADED]

    @overrides
    def set_config_loaded(self):
        env[STANDALONE_CONFIG_LOADED] = True

    @overrides
    def set_env_from_conf(self, conf):
        self.config = conf
        env.user = conf['username']
        env.port = conf['port']
        try:
            env.java_home = conf['java_home']
        except KeyError:
            env.java_home = None
        env.roledefs['coordinator'] = [conf['coordinator']]
        env.roledefs['worker'] = conf['workers']
        env.roledefs['all'] = self._dedup_list(util.get_coordinator_role() +
                                               util.get_worker_role())

        env.hosts = env.roledefs['all'][:]
        env.conf = conf

    @staticmethod
    def _dedup_list(host_list):
        deduped_list = []
        for item in host_list:
            if item not in deduped_list:
                deduped_list.append(item)
        return deduped_list
