#!/opt/rbta/venvs/aldpro-common/bin/python3
import argparse
import json
import shlex
import subprocess
import time
import datetime
import getpass
import sys
import os
import fcntl
import salt.client
from tabulate import tabulate


def parse_args():
    parser = argparse.ArgumentParser()

    parser.add_argument("-d", "--domain", help="Домен", required=True)
    parser.add_argument("-n", "--host", help="Имя хоста", required=True)
    parser.add_argument("-p", "--admin_pwd", help="Пароль администратора домена")
    parser.add_argument("-u","--admin_username", help="Логин администратора домена (только для обновления)", default='admin')
    parser.add_argument("--ip", 
                        help="IP-адрес сетевого интерфейса компьютера, на котором предполагается обслуживать клиентов домена (IP-адрес будущего контроллера домена)", 
                        required=True)
    parser.add_argument("--no-reboot", help="Не перезагружать компьютер после завершения установки", action='store_true')
    parser.add_argument("--setup_syncer", help="Включить в установку Syncer", action='store_true')
    parser.add_argument("--setup_gc", help="Включить в установку Global Catalog", action='store_true')
    parser.add_argument("--update", help="Обновить контроллер домена", action='store_true')
    parser.add_argument("--ipa_extra_params", help="Дополнительные параметры для ipa-server-install")
    parser.add_argument("--ntp_server", nargs='+', type=str, help="Параметр для указания NTP серверов")
    parser.add_argument("--ntp_pool", nargs='+', type=str, help="Параметр для указания NTP пулов")
    parser.add_argument("--force", help="Форсированная установка", action='store_true')
    parser.add_argument("-U","--unattended", help="Тихая установка", action='store_true')
    parser.add_argument("--validate", help="Включить валидацию введенных значений и проверку окружения", action='store_true')

    args = parser.parse_args()

    domain = args.domain
    host = args.host
    admin_pwd = args.admin_pwd
    admin_username = args.admin_username
    ip = args.ip
    setup_syncer = args.setup_syncer
    setup_gc = args.setup_gc
    action = args.update
    ntp_server = args.ntp_server 
    ntp_pool = args.ntp_pool
    force = args.force
    unattended = args.unattended
    validate = True

    if action:
        action = 'update'
    else:
        action = 'install'

    if domain is None or host is None:
        parser.print_help()
        exit(1)
    if ip is None:
        ip = ''
    ipa_extra_params = args.ipa_extra_params
    if ipa_extra_params is None:
        ipa_extra_params = ''
    if admin_pwd is None:
        if sys.stdin.isatty():
            admin_pwd = getpass.getpass(prompt='Введите пароль администратора домена:')
            if admin_pwd == '': 
                print("Пароль не должен быть пустым")
                exit(1)
        else:
            admin_pwd = sys.stdin.readline().rstrip()

    return domain, host, admin_pwd, ip, setup_syncer, setup_gc, ipa_extra_params, action, admin_username, ntp_server, ntp_pool, force, unattended, validate

def run_command_with_show_stdout(command):
    quoted_cmd = shlex.quote(command)
    retcode = subprocess.call(shlex.split(quoted_cmd), shell=True)

    if retcode != 0:
        _, _, _, pillar = get_pillar()
        pillar_string = json.dumps(pillar)
        error_command = "aldpro-salt-call state.apply aldpro.subsystems.common.ldap_subsystem_end_error pillar='{}' concurrent=True -c /srv/aldpro-salt-master/config/ --log-file=/var/log/aldpro-salt/subsystem.log".format(pillar_string)
        subprocess.call(error_command, shell=True)
        grains_command = "aldpro-salt-call grains.delkey is_first_dc -c /srv/aldpro-salt-master/config/ --log-file=/var/log/aldpro-salt/subsystem.log"
        subprocess.call(grains_command, shell=True)
        schedule_command = "aldpro-salt-call schedule.enable --log-file=/var/log/aldpro-salt/subsystem.log"
        subprocess.call(schedule_command, shell=True)
        raise Exception('Произошла ошибка. Превышено максимальное количество попыток. Пожалуйста, попробуйте выполнить команду повторно.\n')

def process_ipa_extra_params(ipa_extra_params):
    # string example: 'param1,param2=value1,param3=value2'
    # return example: '--param1 --param2 value1 --param3 value2'
    if ipa_extra_params is None or ipa_extra_params == '':
        return ''
    result = []
    for param_val in ipa_extra_params.split(','):
        if '=' in param_val:
            param, val = param_val.split('=')
            result.append(f'--{param} {val}')
        else:
            result.append(f'--{param_val}')

    return ' '.join(result)

def get_pillar():
    domain, host, admin_pwd, ip, setup_syncer, setup_gc, ipa_extra_params, action, admin_username, ntp_server, ntp_pool, force, unattended, validate = parse_args()
    processed_ipa_extra_params = process_ipa_extra_params(ipa_extra_params)
    current_time = datetime.datetime.utcnow()
    state_created = current_time.strftime("%Y%m%d%H%M%S") + 'Z'

    pillar = {
        'admin_username': admin_username,
        'admin_password': admin_pwd,
        'domain': domain,
        'host': host,
        'ip': ip,
        'setup_syncer': setup_syncer,
        'action': action,
        'setup_gc': setup_gc,
        'setup_pkiproxy': True,
        'ipa_extra_params': processed_ipa_extra_params,
        'ntp_server': ntp_server,
        'ntp_pool': ntp_pool,
        'aldpro_deploy_data': {
            'action': action,
            'location': 'hq',
            'service_name': 'dc',
            'site': 'Головной офис',
            'state_created': state_created,
            'config': {
                'is_master': True,
                'setup_gc': setup_gc,
                'setup_pkiproxy': True,
                'setup_syncer': setup_syncer,
                }
            } 
    }
    return validate, force, unattended, pillar

def check_status():
    with open('/tmp/aldpro_system_info', 'r') as f:
        status = f.readlines()
        error = True if status[0].strip() == 'True' else False
        warning = True if status[1].strip() == 'True' else False
    return error, warning

def acquire_lock(lock_file):
    try:
        fd = os.open(lock_file, os.O_WRONLY | os.O_CREAT)
        fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
        return fd
    except (IOError, OSError):
        return None

def release_lock(fd):
    if fd:
        fcntl.flock(fd, fcntl.LOCK_UN)
        os.close(fd)

def run():
    validate, force, unattended, pillar = get_pillar()
    if os.geteuid() != 0:
        print('Скрипт должен быть запущен с правами root')
        sys.exit(1)
    print('Прав для выполнения скрипта достаточно')

    lock_file = "/tmp/aldpro-server-install.lock"
    fd = acquire_lock(lock_file)
    if fd is None:
        print("Другой экземпляр приложения уже запущен")
        sys.exit(1)
    print('Блокировка экземпляра приложения успешно выставлена')    

    interval = 60
    action_value = pillar['aldpro_deploy_data']['action']
    hostname = pillar['host'] + '.' + pillar['domain']
    run_command_with_show_stdout('aldpro-salt-call schedule.disable --log-file=/var/log/aldpro-salt/subsystem.log')
    run_command_with_show_stdout('aldpro-salt-call saltutil.sync_all -c /srv/aldpro-salt-master/config/ --log-file=/var/log/aldpro-salt/subsystem.log')
    print('Выполнена синхронизация данных. Ожидаем {} сек\n'.format(interval))
    time.sleep(interval)

    
    print('Выполняется проверка системы и входных значений\n')
    caller = salt.client.Caller(c_path='/srv/aldpro-salt-master/config/minion')
    if action_value == 'update':
        result = caller.cmd(
            'aldpro_validation.system_info_dc_update',
            domain = pillar["domain"],
            host = pillar["host"],
            password = pillar["admin_password"],
            user_admin = pillar["admin_username"]
        )
    else:
        result = caller.cmd(
            'aldpro_validation.system_info_dc',
            domain = pillar["domain"],
            host = pillar["host"],
            password = pillar["admin_password"],
            ip = pillar["ip"]
        )
    print(tabulate(result, tablefmt='grid'))
    error, warning = check_status()
    if not force:
        if error:
            print('\nСистема или входные значения не соответствует требованиям\nПолную информацию о найденных ошибках можно найти в /var/log/aldpro-salt/validation.log\nТребования к системе и входным значениям можно найти в руководстве администратора\n')
            release_lock(fd)
            sys.exit(1)
        
        if not unattended:
            if warning:
                print("\nСистема или входные значения не соответствуют рекомендованным требованиям\nПолную информацию о найденных ошибках можно найти в /var/log/aldpro-salt/validation.log\nРекомендации к системе и входным значениям можно найти в руководстве администратора")
                if input('\nХотите продолжить? [Да/Нет]: ').lower() not in ['y','yes','д','да']:
                    release_lock(fd)
                    sys.exit(1)
    time.sleep(6)

    if action_value == 'update':
        run_command_with_show_stdout('aldpro-salt-call gp_sum.write_gp_pillar targets={} target_type=host -c /srv/aldpro-salt-master/config/ --log-file=/var/log/aldpro-salt/subsystem.log'.format(hostname))

    run_command_with_show_stdout('aldpro-salt-call grains.set is_first_dc True  -c /srv/aldpro-salt-master/config/ --log-file=/var/log/aldpro-salt/subsystem.log')

    pillar_string = json.dumps(pillar)
    run_command_with_show_stdout("aldpro-salt-call state.apply aldpro.dc.states.mp pillar='{}' concurrent=True -c /srv/aldpro-salt-master/config/ --log-file=/var/log/aldpro-salt/subsystem.log".format(pillar_string))
    run_command_with_show_stdout("aldpro-salt-call state.apply aldpro.dc.states.install_first_dc pillar='{}' concurrent=True -c /srv/aldpro-salt-master/config/ --log-file=/var/log/aldpro-salt/subsystem.log".format(pillar_string))
    run_command_with_show_stdout("aldpro-salt-call state.apply aldpro.dc.states.generate_passwords_first_dc pillar='{}' concurrent=True -c /srv/aldpro-salt-master/config/ --log-file=/var/log/aldpro-salt/subsystem.log".format(pillar_string))
    run_command_with_show_stdout('aldpro-salt-call grains.delkey is_first_dc -c /srv/aldpro-salt-master/config/ --log-file=/var/log/aldpro-salt/subsystem.log')
    run_command_with_show_stdout("aldpro-salt-call state.apply aldpro.dc.install pillar='{}' concurrent=True -c /srv/aldpro-salt-master/config/ --log-file=/var/log/aldpro-salt/subsystem.log --state-output=terse --state-verbose=False".format(pillar_string))
    if action_value == 'install':
        run_command_with_show_stdout("aldpro-salt-call state.apply aldpro.subsystems.common.ldap_subsystem_end_install pillar='{}' concurrent=True -c /srv/aldpro-salt-master/config/ --log-file=/var/log/aldpro-salt/subsystem.log".format(pillar_string))
    elif action_value == 'update':
        run_command_with_show_stdout("aldpro-salt-call state.apply aldpro.subsystems.common.ldap_subsystem_end_update pillar='{}' concurrent=True -c /srv/aldpro-salt-master/config/ --log-file=/var/log/aldpro-salt/subsystem.log".format(pillar_string))

    run_command_with_show_stdout("aldpro-salt-call state.apply utils.build_deploy_pillar pillar='{}' concurrent=True -c /srv/aldpro-salt-master/config/ --log-file=/var/log/aldpro-salt/subsystem.log --state-output=terse --state-verbose=False".format(pillar_string))
    run_command_with_show_stdout('aldpro-salt-call schedule.enable --log-file=/var/log/aldpro-salt/subsystem.log')
    
    release_lock(fd)
if __name__ == '__main__':
    run()