#!/usr/bin/python3

import json
import os
import sys
import shutil


def _iter_dir_files(root: str):
    stack = [root]
    while stack:
        d = stack.pop()
        try:
            with os.scandir(d) as it:
                for entry in it:
                    try:
                        if entry.is_dir(follow_symlinks=False):
                            stack.append(entry.path)
                            continue
                        if entry.is_file(follow_symlinks=False):
                            yield entry.path, entry.name
                    except FileNotFoundError:
                        continue
        except FileNotFoundError:
            continue
        except NotADirectoryError:
            continue

def _rm_f(path: str) -> None:
    try:
        os.remove(path)
    except FileNotFoundError:
        return


def _cleanup_generated(backends_dir: str, objects_dir: str, legacy_prefix: str, framework_prefix: str) -> None:
    _rm_f("{}")
    _rm_f("{}")
    _rm_f("{}")

    for path, name in _iter_dir_files(backends_dir):
        if not name.endswith(".backend"):
            continue
        if not (name.startswith(legacy_prefix) or name.startswith(framework_prefix)):
            continue

        _rm_f(path)

    for path, name in _iter_dir_files(objects_dir):
        if not (name.startswith(framework_prefix) and name.endswith(".object")):
            continue

        _rm_f(path)


def _read_manifest_id(manifest_path: str) -> str:
    try:
        with open(manifest_path, "r", encoding="utf-8") as f:
            data = json.load(f)
        module_id = data.get("id", "")
        if module_id is None:
            module_id = ""
        return str(module_id)
    except Exception:
        return ""


def _read_desktop_values(path: str) -> dict[str, str]:
    values: dict[str, str] = {}
    current_section = None
    with open(path, "r", encoding="utf-8", errors="replace") as f:
        for raw_line in f:
            line = raw_line.strip()
            if not line or line.startswith("#"):
                continue
            if line.startswith("[") and line.endswith("]"):
                current_section = line[1:-1]
                continue
            if current_section != "Desktop Entry":
                continue
            if "=" not in line:
                continue
            k, v = line.split("=", 1)
            values[k] = v
    return values


def _toml_str(s: str) -> str:
    return json.dumps(s)


def _write_legacy_backend(backend_path: str, backend_name: str, desktop_path: str, modname: str) -> None:
    content = (
        'type = "Backend"\n'
        'module = "executor"\n'
        f'name = "{backend_name}"\n'
        'interface = "legacy1"\n'
        'action_id = "org.altlinux.alterator.legacy1"\n'
        "\n"
        "[methods.Info]\n"
        f'execute = "cat {desktop_path}"\n'
        "stdout_bytes = true\n"
        "exit_status  = true\n"
        'action_id = "Info"\n'
        "\n"
        "[methods.Run]\n"
        f'execute = "/usr/sbin/alterator-standalone {modname}"\n'
        "stderr_strings = true\n"
        "exit_status    = true\n"
        "timeout = 0\n"
        "\n"
        "[methods.Run.environment.DISPLAY]\n"
        "[methods.Run.environment.XAUTHORITY]\n"
    )
    with open(backend_path, "w", encoding="utf-8") as f:
        f.write(content)


def _write_framework_object(out_path: str, desktop_path: str, modname: str, module_id: str) -> None:
    v = _read_desktop_values(desktop_path)

    name_en = v.get("Name", "") or modname
    name_ru = v.get("Name[ru]", "") or name_en
    categories = v.get("Categories", "")
    icon = v.get("Icon", "")
    weight_raw = v.get("X-Alterator-Weight", "")

    lines: list[str] = [
        'type = "Object"',
        f'name = {_toml_str(modname)}',
        f'display_name.en = {_toml_str(name_en)}',
        f'display_name.ru = {_toml_str(name_ru)}',
        'override = "org.altlinux.alterator.legacy1"',
        f'framework_module_id = {_toml_str(module_id)}',
    ]

    if categories:
        lines.append(f'category = {_toml_str(categories)}')
    if icon:
        lines.append(f'icon = {_toml_str(icon)}')

    try:
        if weight_raw:
            weight = int(weight_raw)
            lines.append(f"weight = {weight}")
    except ValueError:
        pass

    with open(out_path, "w", encoding="utf-8") as f:
        f.write("\n".join(lines))
        f.write("\n")


def _write_framework_backend(backend_path: str, backend_name: str, object_path: str) -> None:
    content = (
        'type = "Backend"\n'
        'module = "executor"\n'
        f'name = "{backend_name}"\n'
        'interface = "framework1"\n'
        'action_id = "org.altlinux.alterator.framework1"\n'
        "\n"
        "[methods.Info]\n"
        f'execute = "cat {object_path}"\n'
        "stdout_bytes = true\n"
        "exit_status  = true\n"
        'action_id = "Info"\n'
    )
    with open(backend_path, "w", encoding="utf-8") as f:
        f.write(content)


def main() -> int:
    backends_dir = "/etc/alterator/backends"
    legacy_dir = "/usr/share/alterator/applications"
    framework_modules_dir = "/usr/share/alterator-framework/modules"
    objects_dir = "/etc/alterator/objects"

    legacy_prefix = "autogenerated-legacy-"
    framework_prefix = "autogenerated-framework-"

    have_alterator_framework = 1 if (shutil.which("alterator-framework") or os.path.exists("/usr/bin/alterator-framework")) else 0

    if not os.path.isdir(backends_dir):
        return 0
    if not os.access(backends_dir, os.W_OK):
        return 0
    if not os.path.isdir(legacy_dir):
        return 0

    if not os.path.isdir(objects_dir):
        try:
            os.makedirs(objects_dir, exist_ok=True)
        except Exception:
            return 0

    if not os.access(objects_dir, os.W_OK):
        return 0

    _cleanup_generated(backends_dir, objects_dir, legacy_prefix, framework_prefix)

    for desktop_path, name in _iter_dir_files(legacy_dir):
        if not name.endswith(".desktop"):
            continue

        modname = name[: -len(".desktop")]
        backend_name = modname.replace("-", "_")

        legacy_backend_path = os.path.join(backends_dir, f"{legacy_prefix}{modname}.backend")
        _write_legacy_backend(legacy_backend_path, backend_name, desktop_path, modname)

        manifest_path = os.path.join(framework_modules_dir, modname, "manifest.json")
        if have_alterator_framework == 1 and os.path.isfile(manifest_path):
            framework_module_id = _read_manifest_id(manifest_path)
            if not framework_module_id:
                framework_module_id = modname

            framework_object_path = os.path.join(objects_dir, f"{framework_prefix}{modname}.object")
            _write_framework_object(framework_object_path, desktop_path, modname, framework_module_id)

            framework_backend_path = os.path.join(backends_dir, f"{framework_prefix}{modname}.backend")
            _write_framework_backend(framework_backend_path, backend_name, framework_object_path)

    return 0


if __name__ == "__main__":
    sys.exit(main())
