#!/usr/bin/python3

#
# Copyright (C) 2025 Nethesis S.r.l.
# SPDX-License-Identifier: GPL-2.0-only
#

# Export the folloing network configuration to /etc/ha:
# - routes
# - ipsec interfaces
# - wireguard interfaces
# - wireguard peers
# This configuration will be imported as disabled on the backup node

import os
import sys
import json
import shutil
import subprocess
from euci import EUci
from nethsec import utils

out_dir = "/etc/ha"

def export_routes():
    routes = {}
    u = EUci()
    for route in utils.get_all_by_type(u, 'network', 'route'):
        routes[route] = u.get_all('network', route)

    with open(os.path.join(out_dir, 'routes'), 'w') as f:
        json.dump(routes, f)

def export_ipsec_interfaces():
    ipsec_interfaces = {}
    u = EUci()
    for interface in utils.get_all_by_type(u, 'network', 'interface'):
        if interface.startswith('ipsec'):
            ipsec_interfaces[interface] = u.get_all('network', interface)

    with open(os.path.join(out_dir, 'ipsec_interfaces'), 'w') as f:
        json.dump(ipsec_interfaces, f)

def export_tun_interfaces():
    tun_interfaces = {}
    u = EUci()
    for interface in utils.get_all_by_type(u, 'network', 'interface'):
        device = u.get('network', interface, 'device', default=None)
        if device is not None and device.startswith('tun'):
            tun_interfaces[interface] = u.get_all('network', interface)

    with open(os.path.join(out_dir, 'tun_interfaces'), 'w') as f:
        json.dump(tun_interfaces, f)

def export_wireguard_interfaces():
    wireguard_interfaces = {}
    u = EUci()
    for interface in utils.get_all_by_type(u, 'network', 'interface'):
        if interface.startswith('wg'):
            wireguard_interfaces[interface] = u.get_all('network', interface)

    with open(os.path.join(out_dir, 'wg_interfaces'), 'w') as f:
        json.dump(wireguard_interfaces, f)

def export_wireguard_peers():
    wireguard_peers = {}
    u = EUci()
    for section in u.get_all('network'):
        if u.get('network', section).startswith('wireguard_'):
            wireguard_peers[section] = u.get_all('network', section)

    with open(os.path.join(out_dir, 'wg_peers'), 'w') as f:
        json.dump(wireguard_peers, f)

def export_wan_interfaces():
    export_interfaces = {}
    u = EUci()
    try:
        wan_interfaces = u.get_all('firewall', 'ns_wan', 'network')
    except Exception:
        wan_interfaces = {}
    for section in utils.get_all_by_type(u, 'network', 'interface'):
        if section in wan_interfaces:
            export_interfaces[section] = u.get_all('network', section)
    with open(os.path.join(out_dir, 'wan_interfaces'), 'w') as f:
        json.dump(export_interfaces, f)

def export_hotspot_mac():
    mac_address = ""
    u = EUci()
    if u.get('dedalo', 'config', 'disabled', default='1') == '0':
        device = u.get('dedalo', 'config', 'interface')
        try:
            with open(f"/sys/class/net/{device}/address", "r") as f:
                mac_address = f.read().strip()
        except FileNotFoundError:
            # If the device file does not exist, we can skip this part
            pass

        if mac_address:
            with open(os.path.join(out_dir, 'hotspot'), 'w') as f:
                json.dump({"device": device, "mac_address": mac_address}, f)

def get_device_by_name(u, name):
    ret = {}
    for section in u.get_all('network'):
        if u.get('network', section, 'name', default=None) == name:
            ret = u.get_all('network', section)
            ret['type'] = 'device'
            ret['key'] = section
            break
    return ret

def find_wan_devices(u, wan_devices):
    # Export wan devices recursively, expanding bonds and bridges
    # Output is a list of devices with all options plus 2 special fields:
    # - type: 'device' or 'interface'
    # - key: the uci section name
    ret = []
    for device_name in wan_devices:
        device = {}
        if not isinstance(device_name, str):
            continue

        if device_name.startswith('@'):
            # Skip alias: it's already included inside the wan interfaces
            continue

        if device_name.startswith('bond-'):
            # It's a bond, export all the slaves
            bond_key = device_name.removeprefix('bond-')
            device = u.get_all('network', bond_key)
            device['type'] = 'interface'
            device['key'] = bond_key
            try:
                slaves = u.get_all('network', bond_key, 'slaves')
            except Exception:
                slaves = []
            ret.extend(find_wan_devices(u, slaves))
        else:
            device = get_device_by_name(u, device_name)
            if device.get('ports'):
                # It's a bridge, export all the ports
                ret.extend(find_wan_devices(u, device.get('ports', [])))

        if device:
            ret.append(device)
    return ret

def export_wan_devices():
    u = EUci()
    wan_devices = find_wan_devices(u, utils.get_all_wan_devices(u))
    with open(os.path.join(out_dir, 'wan_devices'), 'w') as f:
        json.dump(wan_devices, f)

if __name__ == '__main__':
    proc = subprocess.run(["/usr/libexec/rpcd/ns.ha", "call", "status"], capture_output=True, text=True)
    status = json.loads(proc.stdout)
    if status.get("role") != "primary" and status.get("state") != "master":
        # Export must be executed only on primary node when master
        # If the script is executed when the node is not still master, it will export disabled interfaces
        sys.exit(0)

    # Cleanup the directory if it exists
    if os.path.exists(out_dir):
        shutil.rmtree(out_dir)

    os.makedirs(out_dir, exist_ok=True)
    export_wan_devices()
    export_wan_interfaces()
    export_routes()
    export_ipsec_interfaces()
    export_wireguard_interfaces()
    export_wireguard_peers()
    export_tun_interfaces()
    export_hotspot_mac()