#!/usr/bin/env python3
"""
DebTik Network Interface Name Reconciler
Runs before networking.service / network-pre.target on boot.
Reads /etc/debtik/metadata.json (or legacy /etc/proxmox-router/metadata.json).
Matches physical/veth devices by hardware MAC address and renames them to match the target interface name.
Also brings UP interfaces that have configured IP addresses or auto_up set.
"""
import json
import os
import subprocess
import sys
import time

METADATA_PATHS = [
    "/etc/debtik/metadata.json",
    "/etc/proxmox-router/metadata.json"
]

def log(msg):
    print(f"[debtik-ifnames] {msg}", file=sys.stderr)

def get_current_interfaces():
    res = {}
    net_dir = "/sys/class/net"
    if not os.path.exists(net_dir):
        return res
    for dev in os.listdir(net_dir):
        if dev == "lo":
            continue
        addr_file = os.path.join(net_dir, dev, "address")
        if os.path.exists(addr_file):
            try:
                with open(addr_file, "r") as f:
                    mac = f.read().strip().lower()
                if mac and mac != "00:00:00:00:00:00":
                    res[dev] = mac
            except Exception:
                pass
    return res

def rename_device(old_name, new_name):
    try:
        if os.path.exists(f"/sys/class/net/{new_name}"):
            tmp_name = f"tmp{int(time.time() * 1000) % 100000}"
            subprocess.run(["ip", "link", "set", "dev", new_name, "down"], check=False)
            subprocess.run(["ip", "link", "set", "dev", new_name, "name", tmp_name], check=False)
            log(f"Moved conflicting {new_name} -> {tmp_name}")

        subprocess.run(["ip", "link", "set", "dev", old_name, "down"], check=False)
        r = subprocess.run(["ip", "link", "set", "dev", old_name, "name", new_name], capture_output=True, text=True)
        if r.returncode == 0:
            log(f"Successfully renamed {old_name} -> {new_name}")
            return True
        else:
            log(f"Failed to rename {old_name} -> {new_name}: {r.stderr.strip()}")
            return False
    except Exception as e:
        log(f"Exception renaming {old_name} -> {new_name}: {e}")
        return False

def main():
    metadata = None
    for p in METADATA_PATHS:
        if os.path.exists(p):
            try:
                with open(p, "r") as f:
                    metadata = json.load(f)
                break
            except Exception as e:
                log(f"Error reading {p}: {e}")

    if not metadata or "interfaces" not in metadata:
        sys.exit(0)

    target_map = {}
    auto_up_names = set()
    for name, item in metadata["interfaces"].items():
        mac = item.get("mac", "").strip().lower()
        if mac:
            target_map[mac] = name
        if item.get("auto_up") or item.get("ip_addresses"):
            auto_up_names.add(name)

    if not target_map:
        sys.exit(0)

    current_devs = get_current_interfaces()
    for dev_name, mac in list(current_devs.items()):
        if mac in target_map:
            target_name = target_map[mac]
            if dev_name != target_name:
                log(f"Device {dev_name} has MAC {mac}, reconciling to {target_name}")
                if rename_device(dev_name, target_name):
                    current_devs[target_name] = mac
                    del current_devs[dev_name]
                    if target_name in auto_up_names:
                        subprocess.run(["ip", "link", "set", "dev", target_name, "up"], check=False)

if __name__ == "__main__":
    main()
