#
# Copyright 2014 Canonical, Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import datetime
import os
import yaml
import cloudinstall.utils as utils
import logging


log = logging.getLogger('cloudinstall.config')


# The values of these three install types are user-visible strings:
INSTALL_TYPE_SINGLE = ("Single", "Fully containerized OpenStack installation "
                       "on a single machine.")
INSTALL_TYPE_MULTI = ("Multi", "OpenStack installation utilizing MAAS.")
INSTALL_TYPE_LANDSCAPE = ("OpenStack Autopilot",
                          "The Canonical Distribution "
                          "- Enterprise Openstack Install and Management.")

OPENSTACK_RELEASE_LABELS = dict(icehouse="Icehouse (2014.1.3)",
                                juno="Juno (2014.2.2)",
                                kilo="Kilo (2015.1.0)",
                                liberty="Liberty (2015.2.0)",
                                mitaka="Mitaka (2016 devel)")


class ConfigException(Exception):
    pass


class Config:
    def __init__(self, cfg_obj=None, cfg_file=None, save_backups=True):
        if os.getenv("FAKE_API_DATA"):
            self._juju_env = {"bootstrap-config": {'name': "fake",
                                                   'maas-server': "FAKE"}}
        else:
            self._juju_env = None
        self.node_install_wait_interval = 0.2
        if cfg_obj is None:
            self._config = {}
        else:
            self._config = cfg_obj
        self._cfg_file = cfg_file
        self.save_backups = save_backups

    def save(self):
        """ Saves configuration """
        try:
            if self.save_backups and os.path.exists(self.cfg_file):
                datestr = datetime.datetime.now().strftime("%Y-%m-%d-%H:%M:%S")
                backup_path = os.path.join(self.cfg_path, "config-backups")
                backupfilename = "{}/config-{}.yaml".format(backup_path,
                                                            datestr)
                os.makedirs(backup_path, exist_ok=True)
                os.rename(self.cfg_file, backupfilename)
            utils.spew(self.cfg_file,
                       yaml.safe_dump(dict(self._config),
                                      default_flow_style=False))
        except IOError as e:
            raise ConfigException("Unable to save configuration: {}".format(e))

    def install_types(self):
        """ Installer types
        """
        return [INSTALL_TYPE_LANDSCAPE,
                INSTALL_TYPE_MULTI,
                INSTALL_TYPE_SINGLE]

    @property
    def pidfile(self):
        return os.path.join(self.cfg_path, 'openstack.pid')

    @property
    def share_path(self):
        """ base share path
        """
        return "/usr/share/openstack"

    @property
    def tmpl_path(self):
        """ template path """
        return os.path.join(self.share_path, "templates")

    @property
    def cfg_path(self):
        """ top level configuration path """
        if self._cfg_file is None:
            return os.path.join(utils.install_home(), '.cloud-install')
        else:
            return os.path.dirname(self._cfg_file)

    @property
    def cfg_file(self):
        if self._cfg_file is None:
            return os.path.join(self.cfg_path, 'config.yaml')
        else:
            return self._cfg_file

    @property
    def bin_path(self):
        """ scripts located in non-default system path """
        return os.path.join(self.share_path, "bin")

    @property
    def placements_filename(self):
        return os.path.join(self.cfg_path, 'placements.yaml')

    def is_single(self):
        if self.getopt('install_type') and \
           'Single' in self.getopt('install_type'):
            return True
        return False

    def is_multi(self):
        if self.getopt('install_type') and \
           'Multi' in self.getopt('install_type'):
            return True
        return False

    def is_landscape(self):
        if self.getopt('install_type') and \
           'OpenStack Autopilot' in self.getopt('install_type'):
            return True
        return False

    def setopt(self, key, val):
        """ sets config option """
        try:
            self._config[key] = val
            self.save()
        except Exception as e:
            log.error("Failed to set {} in config: {}".format(key, e))

    def getopt(self, key):
        if key in self._config:
            return self._config[key]
        else:
            if hasattr(self, key):
                attr = getattr(self, key)
                return attr() if callable(attr) else attr
            return False

    def juju_path(self):
        """ Returns path where juju environments reside """
        return os.path.join(self.cfg_path, 'juju')

    def juju_home(self, use_expansion=False):
        """ A string representing JUJU_HOME """
        if use_expansion:
            cfg_base = os.path.basename(self.cfg_path)
            home_path = "~/{0}/juju".format(cfg_base)
        else:
            home_path = self.juju_path()
        return "JUJU_HOME={}".format(home_path)

    @property
    def juju_env(self):
        """ parses current juju environment """
        if self._juju_env:
            return self._juju_env

        env_file = None
        if self.is_single():
            env_file = 'local.jenv'

        if self.is_multi() or self.is_landscape():
            env_file = 'maas.jenv'

        if env_file:
            env_path = os.path.join(self.juju_path(), 'environments', env_file)
        else:
            raise ConfigException('Unable to determine installer type.')

        log.debug("Querying juju env in {}".format(env_path))
        if os.path.exists(env_path):
            with open(env_path) as f:
                self._juju_env = yaml.load(f.read().strip())
            return self._juju_env

        raise ConfigException('Unable to load environments file. Is '
                              'juju bootstrapped?')

    @property
    def juju_environments_path(self):
        """ returns absolute path of juju environments.yaml """
        return os.path.join(self.juju_path(), 'environments.yaml')

    def update_environments_yaml(self, key, val, provider='local'):
        """ updates environments.yaml base file """
        if os.path.exists(self.juju_environments_path):
            with open(self.juju_environments_path) as f:
                _env_yaml_raw = f.read()
                env_yaml = yaml.load(_env_yaml_raw)
        else:
            raise ConfigException(
                "{} unavailable, is juju bootstrapped?".format(
                    self.juju_environments_path))
        if key in env_yaml['environments'][provider]:
            env_yaml['environments'][provider][key] = val
        with open(self.juju_environments_path, 'w') as f:
            _env_yaml_raw = yaml.safe_dump(env_yaml, default_flow_style=False)
            f.write(_env_yaml_raw)

    @property
    def juju_api_password(self):
        return self.juju_env['password']
