"""
Persistance locale des règles ACL du mode isolation : restreint certains pairs à des
ports précis plutôt qu'à tout le réseau virtuel (voir firewall_manager.py et
gui.py). Réglage purement local à cette machine — contrairement au réseau/mot de
passe, aucune donnée n'est envoyée au serveur de signalisation, à l'image du mode
isolation lui-même (c'est une restriction sur les connexions ENTRANTES vers CETTE
machine, décidée unilatéralement par son propriétaire).

Format du fichier : {"25.0.0.6": [8080, 25565], ...} — IP virtuelle -> liste de ports
autorisés pour ce pair. Un pair absent du fichier garde l'accès complet au réseau
virtuel (comportement historique du mode isolation, inchangé par défaut).
"""

from __future__ import annotations

import json
import logging
import os
import stat
from pathlib import Path

logger = logging.getLogger("meshamoto-client.acl_rules")


def load_acl_rules(path: Path) -> dict[str, list[int]]:
    if not path.exists():
        return {}
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        logger.warning("Fichier de règles ACL corrompu, ignoré (%s)", path)
        return {}
    if not isinstance(data, dict):
        return {}

    result: dict[str, list[int]] = {}
    for virtual_ip, ports in data.items():
        if not isinstance(virtual_ip, str) or not isinstance(ports, list):
            continue
        valid_ports = sorted({p for p in ports if isinstance(p, int) and 1 <= p <= 65535})
        if valid_ports:
            result[virtual_ip] = valid_ports
    return result


def save_acl_rules(path: Path, rules: dict[str, list[int]]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(rules), encoding="utf-8")
    try:
        os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
    except OSError:
        pass  # Windows : les permissions POSIX ne s'appliquent pas, sans conséquence


def set_peer_ports(path: Path, virtual_ip: str, ports: list[int]) -> dict[str, list[int]]:
    """Définit (ou retire, si `ports` est vide) la restriction pour un pair, persiste
    et retourne l'état complet à jour."""
    rules = load_acl_rules(path)
    valid_ports = sorted({p for p in ports if 1 <= p <= 65535})
    if valid_ports:
        rules[virtual_ip] = valid_ports
    else:
        rules.pop(virtual_ip, None)
    save_acl_rules(path, rules)
    return rules
