#!/usr/bin/env python3

import os
import re
import sys
from datetime import datetime, timedelta
from typing import Dict, List, Tuple

import ldap
from aldpro_logging import SyslogLogger
from django.conf import settings
from ldap.controls import SimplePagedResultsControl

logger = SyslogLogger("aldpro-mp")

BASE_DIR = "/opt/rbta/ad/mgmtportal/api/core"

if BASE_DIR not in sys.path:
    sys.path.insert(0, BASE_DIR)
os.environ["DJANGO_SETTINGS_MODULE"] = "project.settings"


PRESERVED_USER_DELETE_AFTER_DAYS = settings.PRESERVED_USER_DELETE_AFTER_DAYS
APPLDAP_IPA_SERVER = settings.FREEIPA_SERVER
LDAP_USER = settings.LDAP_USER
LDAP_PASSWORD = settings.LDAP_PASSWORD
LDAP_SSL = settings.LDAP_SSL
LDAP_PROTO = "ldaps" if LDAP_SSL else "ldap"
LDAP_PORT = 636 if LDAP_SSL else 389
BASE_DN = settings.BASE_DN
PAGE_CONTROL: SimplePagedResultsControl = SimplePagedResultsControl(True, size=150, cookie="")


def delta_timestamp(user_delete_time: int) -> str:
    """delta_timestamp получить временную метку формата %Y%m%d%H%M%SZ для фильтра

    Args:
        user_delete_time (int): PRESERVED_USER_DELETE_AFTER_DAYS константа удаления

    Returns:
        str: Метка для фильтра формата %Y%m%d%H%M%SZ
    """
    timestamp = datetime.strptime(datetime.utcnow().strftime("%Y%m%d%H%M%SZ"), "%Y%m%d%H%M%SZ") - timedelta(
        days=user_delete_time
    )
    return timestamp.strftime("%Y%m%d%H%M%SZ")


def ldap_disconnect(initialize_ldap: ldap.ldapobject.SimpleLDAPObject) -> None:
    """ldap_disconnect Закрыть соединение к ldap

    Args:
        initialize_ldap (ldap.ldapobject.SimpleLDAPObject): объект соединения
    """
    logger.info("Закрываем соединение с ldap")
    initialize_ldap.unbind()


def ldap_connect(domain_controller: str) -> ldap.ldapobject.SimpleLDAPObject:
    """ldap_connect Открыть соединенеие к ldap

    Args:
        domain_controller (str): Адрес контролера домена, к которому подсодениться

    Returns:
        ldap.ldapobject.SimpleLDAPObject: объект соединения
    """
    logger.info("Открываем соединение с ldap")
    ldap_url = f"{LDAP_PROTO}://{domain_controller}:{LDAP_PORT}"
    initialize_ldap = ldap.initialize(ldap_url)
    try:
        initialize_ldap.simple_bind_s(
            f"uid={LDAP_USER},cn=sysaccounts,cn=etc,{BASE_DN}",
            LDAP_PASSWORD,
        )
    except ldap.LDAPError as e:
        ldap_disconnect(initialize_ldap)
        sys.exit(f"Ошибка соединения с ldap {e}")
    return initialize_ldap


def delete_ldap_entries(domain_controller: str, entries: List[Tuple[str, Dict[str, List[bytes]]]]) -> None:
    """delete_ldap_entries Удалить пользователей, срок нахождения в корзине которых истёк

    Args:
        domain_controller (str):  Адрес контролера домена, к которому подсодениться
        entries (List[Tuple[str, Dict[str, List[bytes]]]]): Перечень пользователей
    """
    logger.info("Выполняем удаление сущностей")
    initialize_ldap = ldap_connect(domain_controller)
    try:
        for entry in entries:
            user_uid = re.search(r"(?<=^uid=)[a-zA-Z0-9_\-\.\$]+", entry[0]).group(0)
            logger.info(f"Выполняем удаление пользователя {user_uid}")
            initialize_ldap.delete(entry[0])
            logger.info(f"Пользователь {user_uid} удалён")
    except ldap.LDAPError as e:
        sys.exit(f"Ошибка при удалении сущностей {e}")
    finally:
        ldap_disconnect(initialize_ldap)


def fetch_ldap_result(
    domain_controller: str,
) -> List[Tuple[str, Dict[str, List[bytes]]]]:
    """fetch_ldap_result Получить перчень пользователей для удаления

    Args:
        domain_controller (str): Адрес контролера домена, к которому подсодениться

    Returns:
        List[Tuple[str, Dict[str, List[bytes]]]]: Перечень пользователей
    """
    logger.info("Выполняем поиск сущностей")
    initialize_ldap = ldap_connect(domain_controller)
    try:
        user_delete_time: str = delta_timestamp(PRESERVED_USER_DELETE_AFTER_DAYS)
        search_timestamp = initialize_ldap.search_ext(
            base=f"cn=deleted users,cn=accounts,cn=provisioning,{BASE_DN}",
            scope=ldap.SCOPE_ONELEVEL,
            filterstr=f"(metaUserRemovedAt<={user_delete_time})",
            attrlist=["cn"],
            serverctrls=[PAGE_CONTROL],
        )
        result = []
        pages = 0
        while True:
            pages += 1
            _, rdata, _, serverctrls = initialize_ldap.result3(search_timestamp)
            result.extend(rdata)
            controls = [
                control for control in serverctrls if control.controlType == SimplePagedResultsControl.controlType
            ]
            if not controls:
                logger.info("The server ignores RFC 2696 control")
                break
            if not controls[0].cookie:
                break
            PAGE_CONTROL.cookie = controls[0].cookie
            _ = initialize_ldap.search_ext(
                base="cn=deleted users,cn=accounts,cn=provisioning,{BASE_DN}",
                scope=ldap.SCOPE_ONELEVEL,
                filterstr=f"(metaUserRemovedAt<={user_delete_time})",
                attrlist=["cn"],
                serverctrls=[PAGE_CONTROL],
            )
    except ldap.LDAPError as e:
        sys.exit(f"Ошибка поиска сущностей {e}")
    finally:
        ldap_disconnect(initialize_ldap)
    return result


def check_and_delete_users():
    users_to_delete = fetch_ldap_result(APPLDAP_IPA_SERVER)
    if users_to_delete:
        delete_ldap_entries(APPLDAP_IPA_SERVER, users_to_delete)


if __name__ == "__main__":
    check_and_delete_users()
