# -*- 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
from calculate.lib.cl_ldap import LDAPConnectError
from calculate.lib.datavars import (ReadonlyVariable, Variable,
                                    VariableError, PasswordError,
                                    VariableInterface)
from calculate.ldap.variables.helpers import (HashHelper,
                                              LdapMaxHelper)
from calculate.unix.variables.helpers import (UnixUserHelper, UnixGroupHelper,
                                              ExistsUserHelper,
                                              ShowFieldsHelper,
                                              FieldsHelper, UnixActionHelper)
from .action import Actions
import re

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

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


class UserNameHelper(VariableInterface):
    """
    Вспомогательный объект для проверки имени группы
    """

    def check_user_name(self, uname):
        if len(uname) > 32:
            raise VariableError(_("Wrong user name length"))
        if ":" in uname:
            raise VariableError(_("Wrong user name"))


class VariableUrUnixLogin(UserNameHelper, UnixUserHelper, UnixActionHelper,
                          Variable):
    """
    Логин настраиваемого пользователя
    """
    untrusted = True
    opt = ("ur_unix_login",)
    metavalue = "USER"

    def init(self):
        self.label = _("Login")
        self.help = _("set user login")

    def get_new(self):
        return ""

    def get_exists(self):
        return self.Get('ur_unix_login_exists')

    def check_new(self, value):
        if not value:
            raise VariableError(_("You should specify login"))
        self.check_user_name(value)
        if value == "list":
            raise VariableError(_("List is using as keyword"))
        if self.search_ldap_user_name(value):
            raise VariableError(_("User %s already exists" % value))
        if self.search_system_user_name(value):
            raise VariableError(
                _("User %s already exists in /etc/passwd") % value)


class VariableUrUnixLoginExists(UnixUserHelper, UnixActionHelper, Variable):
    """
    Логин настраиваемого пользователя
    """
    type = "choice"
    opt = ("ur_unix_login_exists",)
    metavalue = "USER"
    untrusted = True
    guitype = "readonly"

    def init(self):
        self.label = _("Login")
        self.help = _("set user login")

    def get_new(self):
        return ""

    def get_exists(self):
        return ""

    def choice_exists(self):
        return ((user.username,
                 "%s (%s)" % (user.username, user.comment))
                for user in self.iterate_ldap_user("uid=*"))

    def raiseWrongChoice(self, name, choiceVal, value, error):
        if not value:
            raise VariableError(_("Please specify user login"))
        else:
            raise VariableError(_("%s user not found") % value)


class VariableUrUnixBaseDir(Variable):
    """
    Базовая директория для нового пользователя
    """
    value = "/home"
    opt = ('-b', '--base-dir')
    metavalue = "BASE_DIR"

    def init(self):
        self.label = _("Base directory")
        self.help = _("base directory for the home directory of new account")


class VariableUrUnixComment(ExistsUserHelper, UnixActionHelper, Variable):
    """
    Описание учётной записи
    """
    attribute = "comment"
    value_format = "{ldap.ld_base_root.capitalize()} user"
    opt = ('-c', '--comment')
    metavalue = "COMMENT"

    def init(self):
        self.label = _("Full name")
        self.help = _("set fullname of the account")

    def get_new(self):
        return ""
        # self._value_formatter.format(self.value_format, self.Get)


class VariableUrUnixHomePath(ExistsUserHelper, UnixActionHelper, Variable):
    """
    Путь до домашней директории
    """
    value_format = "{unix.ur_unix_base_dir}/{unix.ur_unix_login}"
    opt = ('-d', '--home-dir')
    metavalue = "HOME_DIR"
    check_after = ["ur_unix_login"]

    def init(self):
        self.label = _("Home directory")
        self.help = _("set home directory of the account")

    def get_exists(self):
        return self.Get('ur_unix_home_path_exists')

    def get_new(self):
        return self._value_formatter.format(self.value_format, self.Get)

    def check_exists(self, value):
        if "," in value:
            raise VariableError(_("Wrong home directory"))
        if not value.startswith('/'):
            raise VariableError(_("Home directory path must be absolutly"))


class VariableUrUnixHomePathExists(ExistsUserHelper, UnixActionHelper,
                                   ReadonlyVariable):
    """
    Путь до домашней директории
    """
    attribute = "homedir"


class VariableUrUnixHomePathMove(Variable):
    """
    Перемещать домашнуюю пользовательскую директорию
    """
    type = "bool"
    opt = ('-m', '--move-home')
    value = "off"
    check_after = ["ur_unix_home_path", "ur_unix_home_path_exists"]

    def init(self):
        self.label = _("Move home directory")
        self.help = _("move contents of the home directory to the new location")

    def check_on(self):
        new_path = self.Get('ur_unix_home_path')
        old_path = self.Get('ur_unix_home_path_exists')
        if new_path == old_path:
            raise VariableError(_("You should change home directory"))
        if path.exists(new_path):
            raise VariableError(
                _("New home directory %s exists") % new_path)


class VariableClUnixGroupDefault(Variable):
    """
    Основная группа пользователя по умолчанию
    """
    value = "domain"


class VariableUrUnixPrimaryGroup(ExistsUserHelper, UnixGroupHelper,
                                 UnixActionHelper, Variable):
    """
    ID основной группы
    """
    attribute = "gid"
    type = "choiceedit"
    opt = ('-g', '--gid')
    metavalue = "GROUP"

    @property
    def domain_group(self):
        return self.Get('cl_unix_group_default')

    def init(self):
        self.label = _("Primary group")
        self.help = _("set name or ID of primary group of the account "
                      "('default' value for create default group)")

    def get_new(self):
        return self.domain_group

    def get_exists(self):
        value = str(ExistsUserHelper.get_exists(self))
        if value:
            return str(self.gid_to_name(value))

    def set(self, value):
        return str(self.gid_to_name(value))

    def choice_new(self):
        return ([(self.domain_group, self.domain_group)]
                + self.ldap_group_list())

    def choice_exists(self):
        return self.ldap_group_list()

    def check_new(self, value):
        if not value:
            raise VariableError(_("You should specify primary group ID"))

    def check_exists(self, value):
        if not value:
            raise VariableError(_("You should specify primary group ID"))
        if value:
            self.check_group(value)

    def raiseWrongChoice(self, name, choiceVal, value, error):
        raise VariableError(_("Group %s not found") % value)


class VariableUrUnixGid(UnixGroupHelper, ReadonlyVariable):
    """
    ID основной группы пользователя
    """
    type = "int"

    def get(self):
        group_name = self.Get('ur_unix_primary_group')
        group = self.search_ldap_group_name(group_name)
        if group:
            return str(group.gid)
        else:
            return self.Get('ur_unix_next_gid')


class VariableUrUnixUid(UnixUserHelper, UnixActionHelper, Variable):
    """
    UID пользователя
    """
    type = "int"
    opt = ('-u', '--uid')
    metavalue = "UID"
    value = ""

    def init(self):
        self.label = _("User ID")
        self.help = _("set user ID of the account")

    def get_new(self):
        return self.Get('ur_unix_next_uid')

    def get_exists(self):
        login = self.Get('ur_unix_login')
        if login:
            return str(self.search_ldap_user_name(login).uid)

    def check_new(self, value):
        user = self.search_ldap_user_id(value)
        if user:
            raise VariableError(
                _("Uid {userid} is used by user {username}").format(
                    userid=user.uid, username=user.username))
        user = self.search_system_user_id(value)
        if user:
            raise VariableError(
                _("Uid {userid} is used by system user {username}").format(
                    userid=user.uid, username=user.username))


class VariableUrUnixGroups(UnixGroupHelper, UnixActionHelper, Variable):
    """
    Список груп
    """
    type = "choiceedit-list"
    opt = ("-G", "--groups")
    metavalue = "GROUPS"
    value = []

    def init(self):
        self.label = _("Supplimentary groups")
        self.help = _("set list of supplementary groups of the account")

    def set(self, value):
        return map(self.gid_to_name, value)

    def get_exists(self):
        return self.Get('ur_unix_groups_exists')

    def choice(self):
        return self.ldap_group_list()

    def check(self, value):
        for group in value:
            self.check_group(group)


class VariableUrUnixGroupsExists(UnixGroupHelper, UnixActionHelper, Variable):
    """
    Список груп
    """
    type = "list"
    value = []

    # def get_bylogin(self, login, obj):
    #    return [x.group_name for x in
    #            self.iterate_ldap_group("memberUid=%s" % login)]

    def get_exists(self):
        login = self.Get('ur_unix_login')
        if login:
            return [x.group_name for x in
                    self.iterate_ldap_group("memberUid=%s" % login)]
        return []


class VariableUrUnixGroupsAdd(UnixGroupHelper, UnixActionHelper, Variable):
    """
    Список групп в которые необходимо добавить пользователя
    """
    type = "choiceedit-list"
    guitype = "hidden"
    opt = ("-a", "--append")
    metavalue = "GROUPS"
    value = []

    def init(self):
        self.label = _("Include to groups")
        self.help = _("include user into groups")

    def set(self, value):
        return map(self.gid_to_name, value)

    def get_exists(self):
        exists_groups = set(self.Get('ur_unix_groups_exists'))
        replace_group = set(self.Get('ur_unix_groups'))
        return sorted(list(replace_group - exists_groups))

    def choice_exists(self):
        login = self.Get('ur_unix_login')
        if login:
            exists_groups = self.Get('ur_unix_groups_exists')
            return (x for x in self.ldap_group_list()
                    if x[0] not in exists_groups)
        return []

    def check_exists(self, value):
        exists_groups = self.Get('ur_unix_groups_exists')
        replace_groups = self.Get('ur_unix_groups')
        if set(exists_groups) != set(replace_groups):
            raise VariableError(_(
                "Appending user to groups unavailable "
                "with the replace groups using"))
        login = self.Get('ur_unix_login')
        groups = self.ldap_group_names()
        failed = [x for x in value if x not in groups]
        if failed:
            raise VariableError(
                _("Wrong groups {groups}").format(
                    groups=", ".join(failed)))
        self.check_special(value, exists_groups, login)

    def check_special(self, value, exists_groups, login):
        already_exists = [x for x in value if x in exists_groups]
        if already_exists:
            raise VariableError(
                _("User {user} already exist in groups {groups}").format(
                    groups=", ".join(already_exists), user=login))

    def humanReadable(self):
        if not self.Get():
            return _("Not change")


class VariableUrUnixGroupsDel(VariableUrUnixGroupsAdd):
    """
    Список групп из которых необходимо исключить пользователя
    """
    type = "choiceedit-list"
    opt = ("-r", "--remove")
    guitype = "hidden"
    metavalue = "GROUPS"
    value = []

    def init(self):
        self.label = _("Exclude from groups")
        self.help = _("exclude user from groups")

    def get_exists(self):
        exists_groups = set(self.Get('ur_unix_groups_exists'))
        replace_groups = set(self.Get('ur_unix_groups'))
        return sorted(list(exists_groups - replace_groups))

    def choice_exists(self):
        login = self.Get('ur_unix_login')
        if login:
            exists_groups = self.Get('ur_unix_groups_exists')
            return (x for x in self.ldap_group_list()
                    if x[0] in exists_groups)
        return []

    def check_special(self, value, exists_groups, login):
        not_exists = [x for x in value if x not in exists_groups]
        if not_exists:
            raise VariableError(
                _("User {user} is not exist in groups {groups}").format(
                    groups=", ".join(not_exists), user=login))


class VariableUrUnixSkel(Variable):
    """
    Skel директория
    """
    opt = ("-k", "--skel")
    metavalue = "SKEL_DIR"
    value = "/etc/skel"

    def init(self):
        self.label = _("Skeleton directory")
        self.help = _("use this alternative skeleton directory")


class VariableUrUnixCreateHomeSet(Variable):
    """
    Создавать домашнюю директорию пользователю
    """
    type = "bool"
    opt = ("-m", "--create-home")
    value = "off"
    check_after = ["ur_unix_home_path"]

    def init(self):
        self.label = _("Create home directory")
        self.help = _("create the user's home directory")

    def check_on(self):
        home_path = self.Get('ur_unix_home_path')
        if path.exists(home_path):
            raise VariableError(
                _("Home directory %s already exists") % home_path)


class VariableUrUnixPw(Variable):
    """
    Пароль пользователя
    """
    type = "password"
    opt = ("-p", "--password")
    value = ""
    untrusted = True
    metavalue = "PASSWORD"
    check_after = ["ur_unix_pw_delete_set", 'ur_unix_lock_set']

    def init(self):
        self.label = _("Password")
        self.help = _("set user password")

    def check(self, value):
        delete_pw = self.GetBool('ur_unix_pw_delete_set')
        change_lock = self.GetBool('ur_unix_lock_set')
        if (self.Get('cl_unix_action') == Actions.Passwd and
                not delete_pw and change_lock is None and not value):
            raise PasswordError(_("Specify user password"))
        if self.Get('cl_unix_action') in (Actions.Passwd, Actions.UserMod):
            if self.Get('ur_unix_pw_delete_set') == 'on' and value:
                raise VariableError(_("Can not use remove and "
                                      "set the password options together"))


class VariableUrUnixPwSet(UnixActionHelper, UnixUserHelper, ReadonlyVariable):
    """
    Указан ли пароль у пользователя
    """
    type = "bool"

    def init(self):
        self.label = _("Password")

    def get_exists(self):
        username = self.Get('ur_unix_login')
        pw = self.get_password_hash(username)
        if pw and pw != self.DeletedPassword:
            return "on"
        else:
            return "off"


class VariableUrUnixPwDeleteSet(Variable):
    """
    Удалить пароль пользователя
    """
    type = "bool"
    value = "off"

    opt = ("--delete-password",)

    def init(self):
        self.label = _("Remove user password")
        self.help = _("delete the password for the named account")


class VariableUrUnixHash(HashHelper, ReadonlyVariable):
    """
    Хэш пароля
    """
    source = "unix.ur_unix_pw"


class VariableUrUnixUserObject(UnixUserHelper, UnixActionHelper,
                               ReadonlyVariable):
    """
    Объект данных пользователя
    """

    def get_exists(self):
        login = self.Get('ur_unix_login')
        if login:
            return self.search_ldap_user_name(login) or ""
        return ""


class VariableUrUnixShell(ExistsUserHelper, UnixActionHelper, Variable):
    """
    Командная оболочка по умолчанию для пользователя
    """
    attribute = "shell"
    opt = ("-s", "--shell")
    metavalue = "SHELL"

    def init(self):
        self.label = _("Shell")
        self.help = _("login shell of the account")

    def get_new(self):
        return "/bin/bash"


class VariableUrUnixVisibleSet(ExistsUserHelper, UnixActionHelper, Variable):
    """
    Виден ли пользователь
    """
    attribute = "visible"
    type = "boolauto"
    opt = ("-v", "--visible")

    def init(self):
        self.label = _("Visible")
        self.help = _("set visible of the account")

    def get_new(self):
        return "on"

    def get_exists(self):
        return "auto"


class VariableUrUnixLockSet(ExistsUserHelper, UnixActionHelper, Variable):
    """
    Виден ли пользователь
    """
    attribute = "lock"
    type = "boolauto"
    opt = ("-l", "--lock")

    def init(self):
        self.label = _("Locked")
        self.help = _("lock the account")

    def get(self):
        return "auto"


class VariableUrUnixVisibleFlag(UnixUserHelper, ReadonlyVariable):
    """
    Значение используемое для шаблона
    """

    def get(self):
        return self.visible_to_flag(self.GetBool('ur_unix_visible_set'))


class VariableUrUnixLockFlag(UnixUserHelper, ReadonlyVariable):
    """
    Значение используемое для шаблона
    """

    def get(self):
        return self.lock_to_flag(self.GetBool('ur_unix_lock_set'))


class VariableUrUnixNextUid(LdapMaxHelper, ReadonlyVariable):
    """
    Следующий свободный Uid
    """
    base_dn = "{ld_unix_users_dn}"
    search_filter = "uid=*"
    attr = "uidNumber"

    def get(self):
        value = self.get_max()
        if value is None:
            return self.Get('ur_unix_min_uid')
        else:
            return str(value + 1)


class VariableUrUnixMinUid(Variable):
    """
    Минимальный uid
    """
    type = "int"
    value = "10000"


class VariableUrUnixUserCount(UnixUserHelper, UnixActionHelper,
                              ReadonlyVariable):
    """
    Количество пользователей
    """
    type = "int"

    def get_exists(self):
        return len(self.ldap_user_list())


class VariableClUnixUserAliases(ReadonlyVariable):
    """
    Алиасы для переменных
    """
    type = "table"
    value = [
        # ('login', 'ur_unix_login'),
        ('uid', 'ur_unix_uid'),
        ('gid', 'ur_unix_primary_group'),
        ('comment', 'ur_unix_comment'),
        ('lock', 'ur_unix_lock_set'),
        ('visible', 'ur_unix_visible_set'),
        ('groups', 'ur_unix_groups'),
        ('home', 'ur_unix_home_path'),
        ('shell', 'ur_unix_shell'),
        ('password', 'ur_unix_pw_set'),
    ]


class VariableClUnixUserFields(FieldsHelper, Variable):
    """
    Список полей для вывода данных группы
    """
    alias_variable = "cl_unix_user_aliases"


class VariableClUnixUserShowFields(ShowFieldsHelper, ReadonlyVariable):
    """
    Список переменных полей при отображении списка групп
    """
    alias_variable = "cl_unix_user_aliases"
    source_variable = "cl_unix_user_fields"


class VariableClUnixCreateGroupSet(UnixGroupHelper, ReadonlyVariable):
    """
    Нужно ли создавать primary группу или она уже существует
    """

    def get(self):
        group_name = self.Get('ur_unix_primary_group')
        if self.search_ldap_group("cn={0}".format(group_name)):
            return "off"
        return "on"


class VariableUrUnixUserShow(UnixUserHelper, UnixActionHelper, Variable):
    """
    Фильтр на login
    """
    type = "choiceedit"
    opt = ["ur_unix_user_show"]
    metavalue = "USER"

    def init(self):
        self.label = _("Login")
        self.help = _("show user")

    def get(self):
        return ""

    def choice_exists(self):
        try:
            return [("", "")] + self.ldap_user_list()
        except LDAPConnectError as s:
            raise VariableError(str(s))

    def check_exists(self, value):
        if value:
            if not self.search_ldap_user_name(value):
                raise VariableError(_("%s user not found") % value)

    def raiseWrongChoice(self, name, choiceVal, value, error):
        raise VariableError(_("Wrong user name"))
