#!/usr/bin/env python3

# Standard library imports
import json
import logging
import os
import os.path
import pathlib
import re
import subprocess
import time
import typing

# Third-party imports
import validators

import puavo_ers.asyncio
import puavo_ers.conf
import puavo_ers.prog

_LOGGER = logging.getLogger(os.path.basename(__file__))
_THIS_DIR = os.path.dirname(__file__)

_NAKSU2_CERTS_DIR_PATH = os.path.expanduser("~/.local/share/digabi/naksu2/certs/")


def _read_abitti2server_domain_name() -> typing.Optional[str]:
    try:
        with open(
            os.path.join(_NAKSU2_CERTS_DIR_PATH, "domain.txt"),
            "r",
            encoding="utf-8",
        ) as domain_file:
            domain_name = domain_file.readline().strip()
    except FileNotFoundError:
        return None

    if not validators.domain(domain_name):
        raise RuntimeError("invalid domain name", domain_name)

    if not domain_name.endswith(".koe.abitti.net"):
        raise RuntimeError("invalid domain name", domain_name)

    return domain_name


def _reset_abitti2server_domain_name():
    fqdn = _read_abitti2server_domain_name()
    if fqdn:
        _run_networking("set_domain", fqdn)
    else:
        _run_networking("del_domain")


def _get_wd(name):
    if not re.match(r"^[a-z0-9]{1,32}$", name):
        raise ValueError("invalid working directory name", name)

    wd = os.path.join(os.path.expanduser("~/.puavo/puavo-ers/abitti2server"), name)

    try:
        os.makedirs(wd)
    except FileExistsError:
        pass

    return wd


def _run(prog_args, wd):
    with open(os.path.join(wd, "stderr.log"), "w", encoding="utf-8") as stderr:
        with open(os.path.join(wd, "stdout.log"), "w", encoding="utf-8") as stdout:
            subprocess.check_call(
                prog_args,
                cwd=wd,
                stdout=stdout,
                stderr=stderr,
            )


def _run_networking(*args):
    return _run(
        [
            "sudo",
            "-n",
            os.path.join(_THIS_DIR, "puavo-ers-abitti2server-networking"),
        ]
        + list(args),
        _get_wd("networking"),
    )


def _start_naksu2_certs_dir_monitor(loop):
    pathlib.Path(_NAKSU2_CERTS_DIR_PATH).mkdir(parents=True, exist_ok=True)

    naksu2_certs_dir_monitor = puavo_ers.asyncio.FileMonitor(
        loop,
        _NAKSU2_CERTS_DIR_PATH,
        _naksu2_certs_changed,
    )

    naksu2_certs_dir_monitor.start()

    return naksu2_certs_dir_monitor


async def _naksu2_certs_changed(event):
    _LOGGER.info("ainotify event: %s", event)
    _reset_abitti2server_domain_name()


def _main() -> int:
    try:
        ers_mode = puavo_ers.conf.get_ers_mode()
    except puavo_ers.conf.ValidationError as validation_error:
        return 1

    if ers_mode == "abitti2server_examnet_unmanaged":
        _LOGGER.info("Not managing exam network.")
        return 0

    try:
        interface = puavo_ers.conf.get_ers_abitti2server_interface()
    except puavo_ers.conf.ValidationError as validation_error:
        _LOGGER.error("invalid configuration: %s", str(validation_error))
        return 1

    loop = puavo_ers.asyncio.new_event_loop(logger=_LOGGER)
    naksu2_certs_dir_monitor = None

    _run_networking("start")
    puavo_ers.net.wait_interface(interface, timeout=5)
    try:
        _reset_abitti2server_domain_name()
        naksu2_certs_dir_monitor = _start_naksu2_certs_dir_monitor(loop)
        loop.run_forever()
    finally:
        if naksu2_certs_dir_monitor is not None:
            naksu2_certs_dir_monitor.stop()
        loop.close()
        _run_networking("stop")

    return 0


if __name__ == "__main__":
    puavo_ers.prog.logging_singleton_app(_main, _LOGGER)
