# -*- coding: utf-8 -*-

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

import errno
import json
import os
import subprocess

from xivo import http_json_server
from xivo.http_json_server import CMD_R, CMD_RW


class HAConfigManager(object):
    DEFAULT_HA_CONF_FILE = '/etc/xivo/ha.conf'
    DEFAULT_HA_CONFIG = {
        'node_type': 'disabled',
        'remote_address': ''
    }
    CRONFILE_SLAVE = 'xivo-ha-slave'
    CRONFILE_MASTER = 'xivo-ha-master'

    def __init__(self, postgres_config_updater_factory, cronfile_installer,
                 ha_conf_file=DEFAULT_HA_CONF_FILE):
        self._postgres_config_updater_factory = postgres_config_updater_factory
        self._cronfile_installer = cronfile_installer
        self._ha_conf_file = ha_conf_file

    def get_ha_config(self, args, options):
        return self._read_ha_config()

    def _read_ha_config(self):
        try:
            with open(self._ha_conf_file) as fobj:
                return self._read_ha_config_from_fobj(fobj)
        except IOError as e:
            if e.errno == errno.ENOENT:
                return dict(self.DEFAULT_HA_CONFIG)
            else:
                raise

    def _read_ha_config_from_fobj(self, fobj):
        return json.load(fobj)

    def update_ha_config(self, args, options):
        ha_config = args
        self._write_ha_config(ha_config)
        self._update_postgres(ha_config)
        self._update_cronfiles(ha_config)
        self._manage_services(ha_config)

    def _write_ha_config(self, ha_config):
        with open(self._ha_conf_file, 'wb') as fobj:
            self._write_ha_config_to_fobj(ha_config, fobj)

    def _write_ha_config_to_fobj(self, ha_config, fobj):
        fobj.write(json.dumps(ha_config).encode('utf-8'))

    def _update_postgres(self, ha_config):
        postgres_updater = self._postgres_config_updater_factory(ha_config)
        postgres_updater.update_pg_hba_file()
        postgres_updater.reload_postgres()

    def _update_cronfiles(self, ha_config):
        node_type = ha_config['node_type']
        remote_address = ha_config['remote_address']
        self._cronfile_installer.remove_cronfile(self.CRONFILE_MASTER)
        self._cronfile_installer.remove_cronfile(self.CRONFILE_SLAVE)
        if node_type == 'master':
            self._add_master_cronfile(remote_address)
        elif node_type == 'slave':
            self._add_slave_cronfile(remote_address)

    def _add_master_cronfile(self, remote_address):
        content = '0 * * * * root /usr/sbin/xivo-master-slave-db-replication %s >/dev/null\n' \
                  '0 * * * * root /usr/bin/xivo-sync >/dev/null\n' % remote_address
        self._cronfile_installer.add_cronfile(self.CRONFILE_MASTER, content)

    def _add_slave_cronfile(self, remote_address):
        content = '* * * * * root /usr/sbin/xivo-check-master-status %s >/dev/null\n' % remote_address
        self._cronfile_installer.add_cronfile(self.CRONFILE_SLAVE, content)

    def _manage_services(self, ha_config):
        if ha_config['node_type'] != 'slave':
            command_args = ['/usr/sbin/xivo-manage-slave-services', 'start']
            subprocess.check_call(command_args, close_fds=True)


class _PostgresConfigUpdater(object):
    DEFAULT_CONFIG_DIR = '/var/lib/postgresql/11/main'
    PG_HBA_FILE = 'pg_hba.conf'

    def __init__(self, ha_config, postgres_config_dir=DEFAULT_CONFIG_DIR):
        self._ha_config = ha_config
        self._pg_hba_file = os.path.join(postgres_config_dir, self.PG_HBA_FILE)

    def update_pg_hba_file(self):
        self._clear_host_line_in_pg_hba()
        if self._ha_config['node_type'] == 'slave':
            self._append_host_line_in_pg_hba()

    def _clear_host_line_in_pg_hba(self):
        command_args = ['sed', '-i', '/^host asterisk postgres/d', self._pg_hba_file]
        subprocess.check_call(command_args, close_fds=True)

    def _append_host_line_in_pg_hba(self):
        master_ip_address = self._ha_config['remote_address']
        host_line = 'host asterisk postgres %s/32 trust\n' % master_ip_address
        with open(self._pg_hba_file, 'a') as fobj:
            fobj.write(host_line)

    def reload_postgres(self):
        # Send a SIGHUP to postgres to make postgres re-read the pg_hba.conf.
        # See pg_hba.conf documentation
        command_args = ['/usr/bin/xivo-dcomp', 'kill', '-s', 'SIGHUP', 'db']
        subprocess.check_call(command_args, close_fds=True)


class _CronFileInstaller(object):
    DEFAULT_CRON_DIR = '/etc/cron.d'

    def __init__(self, cron_dir=DEFAULT_CRON_DIR):
        self._cron_dir = cron_dir

    def add_cronfile(self, filename, content):
        abs_filename = os.path.join(self._cron_dir, filename)
        with open(abs_filename, 'w') as fobj:
            fobj.write(content)

    def remove_cronfile(self, filename):
        abs_filename = os.path.join(self._cron_dir, filename)
        try:
            os.unlink(abs_filename)
        except OSError as e:
            if e.errno != errno.ENOENT:
                raise


ha_config_manager = HAConfigManager(_PostgresConfigUpdater, _CronFileInstaller())
http_json_server.register(ha_config_manager.get_ha_config, CMD_R,
                          name='get_ha_config')
http_json_server.register(ha_config_manager.update_ha_config, CMD_RW,
                          name='update_ha_config')
