#!/bin/env /opt/rbta/venvs/aldpro-common/bin/python3
import logging
import os
import sys
from pathlib import Path
from typing import Dict, Optional

from ca import CALess
from exceptions import LoadPemError, PKIProxyTemplateError
from log import setup_log
from settings import Settings

setup_log()
logger = logging.getLogger("plugin")
logger_console = logging.getLogger("console")


def log_error_and_exit(log_message: str, exit_code: int = 1):
    """Вывод сообщений об ошибке и выход из программы

    Args:
        log_message: лог сообщение
        exit_code: код выхода из программы
    """

    if log_message:
        logger.error(log_message)
        logger_console.error(log_message)
    sys.exit(exit_code)


def show_template(template: str, ca: CALess) -> Optional[Dict]:
    """Вывод информации о шаблоне

    Args:
        template: имя шаблона
        ca: экземпляр класса CALess
    Returns:
         Dict: словарь с параметрами шаблона
    """

    try:
        all_templates = ca.templates()
        return all_templates[template.strip()]
    except PKIProxyTemplateError:
        log_error_and_exit("Ошибка чтения файлов шаблонов")
    except KeyError:
        log_error_and_exit(f"Шаблон с именем {template} не найден")


def get_certificate(csr_bytes: bytes, template: str, ca: CALess):
    """Создание сертификата

    Args:
        csr_bytes: CSR в виде строки байт
        template: имя шаблона
        ca: экземпляр класса CALess
    """

    result = None
    all_templates = ca.templates()
    if not ca.load_csr(csr_bytes):
        log_error_and_exit("Ошибка загрузки CSR")

    if template in all_templates.keys():
        logger.info("Обработка запроса на выдачу сертификата")
        try:
            result = ca.generate_certificate(all_templates[template])
        except LoadPemError as e:
            logger.exception(e)
            log_error_and_exit(f"Ошибка доступа к файлам ключей CA: \n{e}")
        except EnvironmentError as e:
            logger.exception(e)
            log_error_and_exit(f"Ошибка с переменными окружения: \n{e}")
        except Exception as e:
            logger.exception(e)
            log_error_and_exit(f"ERROR: \n{e}")

        if result:
            return result.decode()
        else:
            log_error_and_exit(f"Ошибка при обработке запроса на выдачу сертификата для шаблона {template}")
    else:
        log_error_and_exit(f"Неизвестный шаблон {template}")


def register_ca(config: Settings):
    """Регистрация СА. Создание директорий и настройка разрешений

    Args:
        config: экземпляр класса Settings
    """

    try:
        # Создание директорий
        cert_paths = (
            dict(path=Path(os.path.dirname(config.ca_crt_file)), mode=0o755),
            dict(path=Path(os.path.dirname(config.ca_key_file)), mode=0o710),
        )
        for cert_path in cert_paths:
            try:
                logger.debug("Создание директории %s c mode=%s", cert_path['path'], cert_path['mode'])
                cert_path["path"].mkdir(mode=cert_path["mode"], parents=True, exist_ok=True)
            except PermissionError as e:
                logger.error("Ошибка при создании директории %s: %s", cert_path['path'], e)
                return False

        # Создание файлов ca.key и ca.crt, запись значений из переменных окружения
        files = (
            dict(path=Path(config.ca_crt_file), content=config.ca_init_crt, mode=0o644),
            dict(path=Path(config.ca_key_file), content=config.ca_init_key, mode=0o600),
        )
        for file in files:
            logger.debug("Создание файла %s c mode=%s", file['path'], file['mode'])
            try:
                with file["path"].open("wb") as f:
                    f.write(file["content"])
                file["path"].chmod(mode=file["mode"])
            except (PermissionError, FileNotFoundError) as e:
                logger.error("Ошибка при настройке разрешения на файл %s: %s", file['path'], e)
                return False
    except EnvironmentError as e:
        logger.exception("Ошибка чтения переменной окружения: %s", e)
        return False

    return True


def object_dn(config: Settings) -> str:
    """DN объекта, для которого выдается сертификат

    Args:
        config: экземпляр класса Settings
    """

    try:
        return config.object_dn
    except EnvironmentError as e:
        log_error_and_exit(e)


def save_cert_to_file(name: str, data: str):
    """DN объекта, для которого выдается сертификат

    Args:
        name: имя файла
        data: строка для записи в файл
    """

    try:
        with open(name, "w") as stream:
            stream.write(data)
    except (FileNotFoundError, PermissionError) as e:
        logger.error("Ошибка записи сертификата в файл %s: %s", name, e)


def main():
    result_code = 0
    cfg = Settings()

    env_cmd = cfg.pki_cmd
    if env_cmd not in cfg.CMDS:
        log_error_and_exit(f"NOT_IMPLEMENTED: {env_cmd}")

    logger.info("Тип запроса: %s", env_cmd)
    # Зарегистрировать СА
    if env_cmd == "REG_CA":
        if not register_ca(cfg):
            log_error_and_exit("Ошибка при регистрации СА")
        logger.info("Регистрация завершена")
        sys.exit(result_code)

    ca = CALess(cfg)
    # Вывести все шаблоны
    if env_cmd == "GET_TEMPLATES_ALL":
        all_templates = ca.templates()
        logger.info("Получение списка всех шаблонов СА")
        logger_console.info(list(all_templates.keys()))

    # Вывести информацию о выбранном шаблоне
    if env_cmd == "GET_TEMPLATE":
        logger.info("Получение информации о шаблоне СА: %s", cfg.template)
        template = show_template(cfg.template, ca)
        logger_console.info(template)

    # Выдать сертификат
    if env_cmd == "CSR":
        dn = object_dn(cfg)
        logger.info("Обработка запроса на создание сертификата для объекта DN: %s", dn)
        certificate = get_certificate(cfg.csr_bytes, cfg.template, ca)
        logger_console.info(certificate)

        crt_file = os.path.join(cfg.ca_crt_dir, f"{cfg.host}.crt")
        logger.info("Запись сертификата в файл %s", crt_file)
        save_cert_to_file(crt_file, certificate)

    sys.exit(result_code)


if __name__ == "__main__":
    main()
