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

# Copyright 2016 Mir Calculate. http://www.calculate-linux.org
#
#  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.

import sys
from os import path
import hashlib
from calculate.lib.utils.files import makeDirectory, readFile, pathJoin
from calculate.lib.utils.common import genpassword
from calculate.lib.cl_template import FormatFactory
from calculate.core.server.func import CommonLink
from ldap import Ldap
from calculate.core.backup import BackupError
from calculate.lib.cl_ldap import LDIFAdd, LDIFError
import re

_ = lambda x: x
from calculate.lib.cl_lang import (setLocalTranslate, getLazyLocalTranslate)

setLocalTranslate('cl_ldap3', sys.modules[__name__])
__ = getLazyLocalTranslate(_)


class MethodsBase(object):
    def __init__(self, parent):
        self.parent = parent

    def printSUCCESS(self, message):
        self.parent.printSUCCESS(message)

    def printERROR(self, message):
        self.parent.printERROR(message)

    def printWARNING(self, message):
        self.parent.printWARNING(message)


class Backup(MethodsBase):
    """
    Основной объект для выполнения действий связанных
    с настройкой LDAP сервиса
    """

    def __init__(self, parent, clVars):
        self.clVars = clVars
        super(Backup, self).__init__(parent)

    def backup(self, backup_path):
        if self.clVars.GetBool('server.sr_ldap_set'):
            self.clVars.Set('ldap.cl_ldap_bind_dn',
                            self.clVars.Get('ldap.ld_admin_dn'), force=True)
            self.clVars.Set('ldap.cl_ldap_bind_pw',
                            self.clVars.Get('ldap.ld_admin_pw'), force=True)
            con_ldap = self.clVars.Get('ldap.cl_ldap_connect')
            ldap_fn = path.join(backup_path, "ldap", "LDAP_DATABASE.ldif")
            if not path.exists(path.dirname(ldap_fn)):
                makeDirectory(path.dirname(ldap_fn))
            with open(ldap_fn, 'w') as f:
                con_ldap.ldap_dump(f, self.clVars.Get('ldap.ld_base_dn'))

    def _get_basedn(self, slapdconf):
        """
        Получить basedn из файла настроек slapd.conf
        :param slapdconf:
        :return:
        """
        res = re.search(r"^suffix\s*[\"\']?([^'\"]+)[\"\']?$", slapdconf, re.M)
        if res:
            return res.group(1)
        return None

    def restore(self, backup_path):
        """
        Восстановление базы LDAP
        :param backup_path:
        :return:
        """
        ldap = Ldap()
        ldap.clVars = self.clVars
        CommonLink.link_object(self.parent, ldap)

        slapconf_fn = "/etc/openldap/slapd.conf"
        slapconf = readFile(pathJoin(backup_path, "root", slapconf_fn))
        ldif_fn = path.join(backup_path, "ldap", "LDAP_DATABASE.ldif")
        system_md5 = hashlib.md5(readFile(slapconf_fn)).hexdigest().strip()

        if slapconf and path.exists(ldif_fn):
            self.parent.startTask(_("Restoring LDAP"))
            # восстановление файла вместе правами
            self.parent.restore_files(backup_path, ['/etc/openldap/slapd.conf'],
                                      notapply=True)
            # создание локального пароля для администратора LDAP
            basedn = self._get_basedn(slapconf)
            admindn = "cn=ldapadmin,%s" % basedn
            adminpw = genpassword(8)

            ldap.set_ldap_connection(admindn, adminpw)
            format_factory = FormatFactory(None)
            ldap_class = format_factory.getClassObj("ldap")
            source_cfg = ldap_class(slapconf)
            changes = ldap_class(('rootdn          "{admindn}"\n'
                                  "rootpw          {adminhash}").format(
                admindn=admindn,
                adminhash=self.clVars.Get('ldap.cl_ldap_bind_hash')
            ))
            source_cfg.join(changes)
            with open(slapconf_fn, 'w') as f:
                f.write(source_cfg.getConfig())

            # очистка базы
            ldap.stop_service("slapd")
            database_path = self.clVars.Get('ldap.ld_database_path')
            ldap.remove_ldap_db(database_path)
            if not ldap.start_service("slapd"):
                raise BackupError(_("Failed to start LDAP"))

            # импорт базы
            ldap_connect = self.clVars.Get('ldap.cl_ldap_connect')

            try:
                LDIFAdd(readFile(ldif_fn), ldap_connect.conLdap).parse()
            except LDIFError as e:
                self.printERROR("LDIF: %s" % str(e))
                raise BackupError(_("Failed to restore LDAP"))

            # восстановление настроек из резервной копии
            self.parent.restore_files(backup_path, ['/etc/openldap/slapd.conf'],
                                      notapply=True)
            if not ldap.restart_service("slapd"):
                raise BackupError(_("Failed to start LDAP"))
            backup_md5 = hashlib.md5(readFile(slapconf_fn)).hexdigest().strip()
            if backup_md5 != system_md5:
                self.parent.apply_files.add(slapconf_fn)
            self.parent.endTask(True)
