#!/usr/bin/env python3
# coding=utf-8
"""
Генератор конфигов сервисов в зависимости от "размера" виртуалки.
В основном влияет на потребляемую оперативную память.
Т.к. работает в т.ч. на supervisor, поддерживаем совместимость с py3.4!
- Нельзя использовать f-strings!
"""

import argparse
import datetime
import logging
import math
import os
import sys
from pathlib import Path
from hashlib import md5

import jinja2
import yaml


def parse_args() -> argparse.Namespace:
    """ :return: уже разобранные аргументы """
    parser = argparse.ArgumentParser()
    parser.add_argument('--sla', type=str, required=True)
    parser.add_argument('--redis', default=False, action='store_true')
    parser.add_argument('--postgres', default=False, action='store_true')
    parser.add_argument('--uwsgi', default=False, action='store_true')
    parser.add_argument('--uwsgi-acc', default=False, action='store_true')
    parser.add_argument('--uwsgi-msg', default=False, action='store_true')
    parser.add_argument('--uwsgi-converter', default=False, action='store_true')
    parser.add_argument('--disk', default=False, action='store_true')
    parser.add_argument('--mem-guarantee-pct', default=False, action='store_true')
    parser.add_argument('--mem-max', default=False, action='store_true')
    # А пусть и nginx для тестирования настраивает!
    parser.add_argument('--nginx', default=False, action='store_true')
    parser.add_argument('--domain', type=str)
    parser.add_argument('--wildcard', default=False, action='store_true')
    parser.add_argument('--angular-support', default=False, action='store_true')
    parser.add_argument('--redirect-from', nargs='+', type=str)

    return parser.parse_args()


def gen_config(template: Path, dst: Path, params: dict) -> None:
    """
    :param template: путь к jinja-шаблону
    :param dst: путь к конфигу в продакшне
    :param params: параметры, которые нужно подставить в шаблон
    """
    with template.open() as fp:
        tmplt = jinja2.Template(fp.read())
    result = tmplt.render(**params) + '\n'
    if dst.exists():
        result_md5 = md5(result.encode()).hexdigest()
        prev = dst.read_bytes()
        prev_md5 = md5(prev).hexdigest()
        logging.debug("new md5: %s, current md5: %s", result_md5, prev_md5)
        if prev_md5 == result_md5:
            logging.info("%s already exists and OK, skipped.", dst)
            return
        hash_link = dst.with_suffix(dst.suffix + '.' + result_md5)
        if hash_link.exists():
            logging.info("Hash link %s for %s already exists", hash_link, dst)
        else:
            logging.info("%s needs to be updated, we'll keep backup in %s", dst, hash_link)
            dst.rename(hash_link)
        human_path = dst.with_suffix(datetime.datetime.now().strftime(dst.suffix + ".%Y-%m-%d_%H%M%S"))
        if not human_path.exists():
            logging.info("We'll keep symlink %s -> %s", human_path, hash_link)
            human_path.symlink_to(hash_link)
    with dst.open('w') as fp:
        logging.info('Configuring %s ...done', dst)
        fp.write(result)


def main():
    """ Получаем аргументом размер виртуалки, ищем его в основном конфиге и генерируем из него конфиги сервисов """
    pid = os.getpid()
    logging.basicConfig(level=logging.INFO, format="%(message)s")
    logging.info("%s [%d] START", ' '.join(sys.argv), pid)
    args = parse_args()
    file_path = Path('/opt/fox_acrm/custom/profiles.yml')
    if not file_path.exists():
        file_path = Path('/opt/fox_acrm/deploy/profiles.yml')
    service = 'msg' if Path('/opt/messenger').is_dir() else 'acc' if Path('/opt/account').is_dir() else 'crm'
    if not file_path.exists():
        logging.warning("Пытаемся использовать профили, лежащие по относительному пути!")
        logging.warning("Это ок только если это - машина разработчика.")
        file_path = Path('./deploy/profiles.yml')
    with file_path.open() as fp:
        profiles = yaml.load(fp, yaml.SafeLoader)
    sla, templates = profiles.get(args.sla), Path('/opt/fox_acrm/contrib')
    if not templates.exists() and Path('./contrib').exists():
        logging.warning("Пытаемся использовать шаблоны, лежащие по относительному пути!")
        templates = Path('./contrib')
    if args.postgres:
        config = Path('/etc/postgresql/13/main/postgresql.conf')
        gen_config(templates / 'postgresql.conf.jinja2', config, sla['postgres'])
    if args.redis:
        # Для messenger нужен redis, работающий на локальном tcp-порту, т.к. go-socket.io не поддерживает unix-сокеты
        # https://github.com/googollee/go-socket.io/issues/434
        sla['redis']['service'] = service
        gen_config(templates / 'redis.conf.jinja2', Path('/etc/redis/redis.conf'), sla['redis'])
    # uwsgi/uwsgi-acc взаимоисключающие опции
    if args.uwsgi:
        gen_config(templates / 'vassals_web.ini.jinja2', Path('/opt/crm/vassals/web.ini'), sla['uwsgi'])
    elif args.uwsgi_acc:
        gen_config(templates / 'vassals_web.ini.jinja2', Path('/opt/account/vassals/web.ini'), sla['uwsgi'])
    elif args.uwsgi_msg:
        gen_config(templates / 'vassals_web.ini.jinja2', Path('/opt/messenger/vassals/web.ini'), sla['uwsgi'])
    elif args.uwsgi_converter:
        gen_config(templates / 'vassals_web.ini.jinja2', Path('/opt/converter/vassals/web.ini'), sla['uwsgi'])
    if args.nginx and args.domain:
        # Удаление предыдущих конфигов - дело вызывающей стороны
        template = templates / 'self-signed-cert-nginx.conf.jinja2'
        # не абсолютный путь, т.к. копированием занимается nginx_install_certs()
        config = Path((args.domain.split('.', 1)[-1] if args.wildcard else args.domain) + ".conf")
        params = {
            'wildcard': args.wildcard,
            'angular_support': args.angular_support,
            'redirect_from': args.redirect_from,
            'domain': args.domain
        }
        gen_config(template, config, params)
    # Ниже не рендерим конфиги, а возвращаем _одно_ значение. Одно — не может проверить порядок args.
    # А раз только одно — обязательно используем elif.
    if args.mem_guarantee_pct:
        print(math.ceil(100 * sla['system']['ram']['guarantee'] / sla['system']['ram']['max']))
    elif args.mem_max:
        print(sla['system']['ram']['max'])
    elif args.disk:
        print(sla['system']['disk'])
    logging.info("%s [%d] SUCCESS", ' '.join(sys.argv), pid)


if __name__ == '__main__':
    main()
