# -*- 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)
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 VariableUrUnixLogin(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"))
        if not re.match("^[a-zA-Z_]([a-zA-Z0-9_ -]*[a-zA-Z0-9_-])?$", value):
            raise VariableError(_("Wrong user login"))
        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 %s is used by user %s")
                                % (user.uid, user.username))
        user = self.search_system_user_id(value)
        if user:
            raise VariableError(_("Uid %s is used by system user %s")
                                % (user.uid, 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 = _("set skeletion directory for new account")


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"))
