#!/usr/bin/env python3
"""
Extract ipt-firewall configuration from a host and generate host_vars YAML.

Reads /etc/ipt-firewall/{interfaces,main}_ipv{4,6}.conf via SSH,
maps all variables to Ansible fw_* names, and writes a host_vars file.

Usage:
    ./extract-fw-host-vars.py <hostname> [--user USER] [--port PORT] [--dry-run]

Example:
    ./extract-fw-host-vars.py cl-01.oopen.de
    ./extract-fw-host-vars.py cl-01.oopen.de --user root --dry-run
"""

import argparse
import re
import subprocess
import sys
from pathlib import Path


# ---------------------------------------------------------------------------
# Defaults matching roles/ipt-firewall/defaults/main.yml
# Only values that differ from these will be emitted.
# ---------------------------------------------------------------------------
DEFAULTS = {
    "fw_do_not_firewall_bridged_traffic": False,
    "fw_do_not_firewall_lx_guest_systems": False,
    "fw_drop_icmp": False,
    "fw_drop_mndp": True,
    "fw_drop_mdns": True,
    "fw_allow_all_outgoing_traffic": False,
    "fw_blocked_ifs": "",
    "fw_unprotected_ifs": "",
    "fw_forward_private_ips_v4": "",
    "fw_forward_private_ips_v6": "",
    "fw_restrict_local_service_to_net_v4": "",
    "fw_restrict_local_service_to_net_v6": "",
    "fw_restrict_local_net_to_net_v4": "",
    "fw_restrict_local_net_to_net_v6": "",
    "fw_allow_ext_service_v4": "",
    "fw_allow_ext_service_v6": "",
    "fw_allow_ext_net_v4": "",
    "fw_allow_ext_net_v6": "",
    "fw_allow_local_service_v4": "",
    "fw_allow_local_service_v6": "",
    "fw_allow_local_service_from_networks_v4": "",
    "fw_allow_local_service_from_networks_v6": "",
    "fw_vpn_server_ips": "",
    "fw_forward_vpn_server_ips": "",
    "fw_vpn_ports": "$standard_vpn_port",
    "fw_wireguard_server_ips": "",
    "fw_forward_wireguard_server_ips": "",
    "fw_wireguard_server_ports": "$standard_wireguard_port",
    "fw_wireguard_out_ports": "$standard_wireguard_port",
    "fw_local_ntp_service": False,
    "fw_ntp_port": "$standard_ntp_port",
    "fw_ntp_allowed_net": "",
    "fw_dhcp_server_ifs": "",
    "fw_dhcp_client_ifs": "",
    "fw_dns_server_ips": "",
    "fw_forward_dns_server_ips": "",
    "fw_local_resolver_service": False,
    "fw_resolver_port": "$standard_dns_port",
    "fw_resolver_allowed_networks_v4": "",
    "fw_resolver_allowed_networks_v6": "",
    "fw_ssh_server_ips": "$ext_ips",
    "fw_forward_ssh_server_ips": "",
    "fw_ssh_ports": "$standard_ssh_port",
    "fw_http_server_ips": "",
    "fw_forward_http_server_ips": "",
    "fw_http_ports": "$standard_http_ports",
    "fw_log_cgi_traffic_out": False,
    "fw_cgi_script_users": "",
    "fw_mm_server_ips": "",
    "fw_forward_mm_server_ips": "",
    "fw_smtpd_ips": "",
    "fw_forward_smtpd_ips": "",
    "fw_smtpd_additional_listen_ports": "",
    "fw_smtpd_additional_outgoing_ports": "",
    "fw_mail_server_ips": "",
    "fw_forward_mail_server_ips": "",
    "fw_mail_user_ports": "$standard_mailuser_ports",
    "fw_mail_client_ips": "",
    "fw_forward_mail_client_ips": "",
    "fw_dovecot_auth_service": False,
    "fw_dovecot_auth_port": "$dovecot_external_auth_port",
    "fw_dovecot_auth_allowed_networks_v4": "",
    "fw_dovecot_auth_allowed_networks_v6": "",
    "fw_ftp_server_ips": "",
    "fw_forward_ftp_server_ips": "",
    "fw_ftp_passive_port_range": "50000:50400",
    "fw_xmpp_server_ips": "",
    "fw_forward_xmpp_server_ips": "",
    "fw_xmmp_tcp_in_ports": "5222 5223 5269",
    "fw_xmmp_tcp_out_ports": "5269",
    "fw_xmmp_remote_out_services_v4": "",
    "fw_xmmp_remote_out_services_v6": "",
    "fw_mumble_server_ips": "",
    "fw_forward_mumble_server_ips": "",
    "fw_mumble_ports": "$standard_mumble_port",
    "fw_jitsi_server_ips": "",
    "fw_forward_jitsi_server_ips": "",
    "fw_jitsi_tcp_ports": "$standard_jitsi_tcp_ports",
    "fw_jitsi_udp_port_range": "$standard_jitsi_udp_port_range",
    "fw_jitsi_tcp_ports_out": "$standard_turn_service_ports,4443,4444,4445,4446",
    "fw_jitsi_udp_ports_out": "$standard_http_ports,$standard_turn_service_ports,4443,4444,4445,4446",
    "fw_jitsi_dovecot_auth": False,
    "fw_jitsi_dovecot_host": "",
    "fw_jitsi_jibri_remote_auth": False,
    "fw_jitsi_jibri_remote_ips": "",
    "fw_jibri_server_ips": "",
    "fw_forward_jibri_server_ips": "",
    "fw_jibri_remote_jitsi_server": "",
    "fw_nc_turn_server_ips": "",
    "fw_forward_nc_turn_server_ips": "",
    "fw_nc_turn_ports": "$standard_turn_service_ports",
    "fw_nc_turn_udp_ports": "$standard_turn_service_udp_ports",
    "fw_tftp_server_ips": "",
    "fw_prometheus_local_server_ips": "",
    "fw_prometheus_local_client_ips": "",
    "fw_prometheus_remote_server_ips": "",
    "fw_munin_server_ips": "",
    "fw_forward_munin_server_ips": "",
    "fw_munin_remote_port": "$standard_munin_port",
    "fw_munin_local_port": "4949",
    "munin_remote_ipv4": "",
    "munin_remote_ipv6": "",
    "fw_xymon_server_ips": "",
    "fw_local_xymon_client": False,
    "fw_xymon_port": "$standard_xymon_port",
    "fw_rsync_out_ips": "",
    "fw_forward_rsync_out_ips": "",
    "fw_rsync_ports": "873",
    "fw_tcp_out_ports": "",
    "fw_forward_tcp_out_ports": "",
    "fw_udp_out_ports": "",
    "fw_forward_udp_out_ports": "",
    "fw_portforward_tcp_v4": "",
    "fw_portforward_udp_v4": "",
    "fw_portforward_tcp_v6": "",
    "fw_portforward_udp_v6": "",
    "fw_blocked_ips": "",
    "fw_block_tcp_ports": "111 113 135 137:139 445",
    "fw_block_udp_ports": "111 137:139",
    "fw_create_traffic_counter": True,
    "fw_create_iperf_rules": True,
    "fw_protection_against_syn_flooding": True,
    "fw_protection_against_port_scanning": True,
    "fw_protection_against_ssh_brute_force_attacks": True,
    "fw_limit_connections_per_source_IP": True,
    "fw_per_IP_connection_limit": "$default_per_IP_connection_limit",
    "fw_limit_new_tcp_connections_per_seconds_per_source_IP": True,
    "fw_limit_new_tcp_connections_per_seconds_ports": "",
    "fw_kernel_activate_forwarding": False,
    "fw_kernel_support_dynaddr": False,
    "fw_dynaddr_flag": "5",
    "fw_kernel_reduce_timeouts": True,
    "fw_kernel_tcp_syncookies": True,
    "fw_kernel_protect_against_icmp_bogus_messages": True,
    "fw_kernel_ignore_broadcast_ping": True,
    "fw_kernel_deactivate_source_route": True,
    "fw_kernel_dont_accept_redirects": True,
    "fw_kernel_activate_rp_filter": True,
    "fw_kernel_log_martians": False,
    "fw_kernel_forward_between_interfaces": False,
    "fw_vpn_ifs": "tun+",
    "fw_wg_ifs": "wg+",
    "fw_nat_devices": "",
}

# ---------------------------------------------------------------------------
# Variable mapping: (bash_varname, source) → ansible_varname
# source: 'iface_v4', 'iface_v6', 'main_v4', 'main_v6', 'main_shared'
# ---------------------------------------------------------------------------

# Shared service variables (read from main_ipv4.conf, same in both)
MAIN_SHARED = {
    "do_not_firewall_bridged_traffic": "fw_do_not_firewall_bridged_traffic",
    "do_not_firewall_lx_guest_systems": "fw_do_not_firewall_lx_guest_systems",
    "drop_icmp": "fw_drop_icmp",
    "drop_mndp": "fw_drop_mndp",
    "drop_mdns": "fw_drop_mdns",
    "allow_all_outgoing_traffic": "fw_allow_all_outgoing_traffic",
    "blocked_ifs": "fw_blocked_ifs",
    "unprotected_ifs": "fw_unprotected_ifs",
    "vpn_server_ips": "fw_vpn_server_ips",
    "forward_vpn_server_ips": "fw_forward_vpn_server_ips",
    "vpn_ports": "fw_vpn_ports",
    "wireguard_server_ips": "fw_wireguard_server_ips",
    "forward_wireguard_server_ips": "fw_forward_wireguard_server_ips",
    "wireguard_server_ports": "fw_wireguard_server_ports",
    "wireguard_out_ports": "fw_wireguard_out_ports",
    "local_ntp_service": "fw_local_ntp_service",
    "ntp_port": "fw_ntp_port",
    "ntp_allowed_net": "fw_ntp_allowed_net",
    "dns_server_ips": "fw_dns_server_ips",
    "forward_dns_server_ips": "fw_forward_dns_server_ips",
    "local_resolver_service": "fw_local_resolver_service",
    "resolver_port": "fw_resolver_port",
    "ssh_server_ips": "fw_ssh_server_ips",
    "forward_ssh_server_ips": "fw_forward_ssh_server_ips",
    "ssh_ports": "fw_ssh_ports",
    "http_server_ips": "fw_http_server_ips",
    "forward_http_server_ips": "fw_forward_http_server_ips",
    "http_ports": "fw_http_ports",
    "log_cgi_traffic_out": "fw_log_cgi_traffic_out",
    "cgi_script_users": "fw_cgi_script_users",
    "mm_server_ips": "fw_mm_server_ips",
    "forward_mm_server_ips": "fw_forward_mm_server_ips",
    "smtpd_ips": "fw_smtpd_ips",
    "forward_smtpd_ips": "fw_forward_smtpd_ips",
    "smtpd_additional_listen_ports": "fw_smtpd_additional_listen_ports",
    "smtpd_additional_outgoung_ports": "fw_smtpd_additional_outgoing_ports",
    "mail_server_ips": "fw_mail_server_ips",
    "forward_mail_server_ips": "fw_forward_mail_server_ips",
    "mail_user_ports": "fw_mail_user_ports",
    "mail_client_ips": "fw_mail_client_ips",
    "forward_mail_client_ips": "fw_forward_mail_client_ips",
    "dovecot_auth_service": "fw_dovecot_auth_service",
    "dovecot_auth_port": "fw_dovecot_auth_port",
    "ftp_server_ips": "fw_ftp_server_ips",
    "forward_ftp_server_ips": "fw_forward_ftp_server_ips",
    "ftp_passive_port_range": "fw_ftp_passive_port_range",
    "xmpp_server_ips": "fw_xmpp_server_ips",
    "forward_xmpp_server_ips": "fw_forward_xmpp_server_ips",
    "xmmp_tcp_in_ports": "fw_xmmp_tcp_in_ports",
    "xmmp_tcp_out_ports": "fw_xmmp_tcp_out_ports",
    "mumble_server_ips": "fw_mumble_server_ips",
    "forward_mumble_server_ips": "fw_forward_mumble_server_ips",
    "mumble_ports": "fw_mumble_ports",
    "jitsi_server_ips": "fw_jitsi_server_ips",
    "forward_jitsi_server_ips": "fw_forward_jitsi_server_ips",
    "jitsi_tcp_ports": "fw_jitsi_tcp_ports",
    "jitsi_udp_port_range": "fw_jitsi_udp_port_range",
    "jitsi_tcp_ports_out": "fw_jitsi_tcp_ports_out",
    "jitsi_udp_ports_out": "fw_jitsi_udp_ports_out",
    "jitsi_dovecot_auth": "fw_jitsi_dovecot_auth",
    "jitsi_dovecot_host": "fw_jitsi_dovecot_host",
    "jitsi_jibri_remote_auth": "fw_jitsi_jibri_remote_auth",
    "jitsi_jibri_remote_ips": "fw_jitsi_jibri_remote_ips",
    "jibri_server_ips": "fw_jibri_server_ips",
    "forward_jibri_server_ips": "fw_forward_jibri_server_ips",
    "jibri_remote_jitsi_server": "fw_jibri_remote_jitsi_server",
    "nc_turn_server_ips": "fw_nc_turn_server_ips",
    "forward_nc_turn_server_ips": "fw_forward_nc_turn_server_ips",
    "nc_turn_ports": "fw_nc_turn_ports",
    "nc_turn_udp_ports": "fw_nc_turn_udp_ports",
    "tftp_server_ips": "fw_tftp_server_ips",
    "prometheus_local_server_ips": "fw_prometheus_local_server_ips",
    "prometheus_local_client_ips": "fw_prometheus_local_client_ips",
    "prometheus_remote_server_ips": "fw_prometheus_remote_server_ips",
    "munin_server_ips": "fw_munin_server_ips",
    "forward_munin_server_ips": "fw_forward_munin_server_ips",
    "munin_remote_port": "fw_munin_remote_port",
    "munin_local_port": "fw_munin_local_port",
    "xymon_server_ips": "fw_xymon_server_ips",
    "local_xymon_client": "fw_local_xymon_client",
    "xymon_port": "fw_xymon_port",
    "rsync_out_ips": "fw_rsync_out_ips",
    "forward_rsync_out_ips": "fw_forward_rsync_out_ips",
    "rsync_ports": "fw_rsync_ports",
    "tcp_out_ports": "fw_tcp_out_ports",
    "forward_tcp_out_ports": "fw_forward_tcp_out_ports",
    "udp_out_ports": "fw_udp_out_ports",
    "forward_udp_out_ports": "fw_forward_udp_out_ports",
    "blocked_ips": "fw_blocked_ips",
    "block_tcp_ports": "fw_block_tcp_ports",
    "block_udp_ports": "fw_block_udp_ports",
    "create_traffic_counter": "fw_create_traffic_counter",
    "create_iperf_rules": "fw_create_iperf_rules",
    "protection_against_syn_flooding": "fw_protection_against_syn_flooding",
    "protection_against_port_scanning": "fw_protection_against_port_scanning",
    "protection_against_ssh_brute_force_attacks": "fw_protection_against_ssh_brute_force_attacks",
    "limit_connections_per_source_IP": "fw_limit_connections_per_source_IP",
    "per_IP_connection_limit": "fw_per_IP_connection_limit",
    "limit_new_tcp_connections_per_seconds_per_source_IP": "fw_limit_new_tcp_connections_per_seconds_per_source_IP",
    "limit_new_tcp_connections_per_seconds_ports": "fw_limit_new_tcp_connections_per_seconds_ports",
}

# IPv4-only variables (from main_ipv4.conf)
MAIN_V4_ONLY = {
    "forward_private_ips": "fw_forward_private_ips_v4",
    "restrict_local_service_to_net": "fw_restrict_local_service_to_net_v4",
    "restrict_local_net_to_net": "fw_restrict_local_net_to_net_v4",
    "allow_ext_service": "fw_allow_ext_service_v4",
    "allow_ext_net": "fw_allow_ext_net_v4",
    "allow_local_service": "fw_allow_local_service_v4",
    "allow_local_service_from_networks": "fw_allow_local_service_from_networks_v4",
    "portforward_tcp": "fw_portforward_tcp_v4",
    "portforward_udp": "fw_portforward_udp_v4",
    "munin_remote_ip": "munin_remote_ipv4",
    "dovecot_auth_allowed_networks": "fw_dovecot_auth_allowed_networks_v4",
    "xmmp_remote_out_services": "fw_xmmp_remote_out_services_v4",
    "resolver_allowed_networks": "fw_resolver_allowed_networks_v4",
    "dhcp_server_ifs": "fw_dhcp_server_ifs",
    "dhcp_client_ifs": "fw_dhcp_client_ifs",
    "kernel_activate_forwarding": "fw_kernel_activate_forwarding",
    "kernel_support_dynaddr": "fw_kernel_support_dynaddr",
    "dynaddr_flag": "fw_dynaddr_flag",
    "kernel_reduce_timeouts": "fw_kernel_reduce_timeouts",
    "kernel_tcp_syncookies": "fw_kernel_tcp_syncookies",
    "kernel_protect_against_icmp_bogus_messages": "fw_kernel_protect_against_icmp_bogus_messages",
    "kernel_ignore_broadcast_ping": "fw_kernel_ignore_broadcast_ping",
    "kernel_activate_rp_filter": "fw_kernel_activate_rp_filter",
    "kernel_log_martians": "fw_kernel_log_martians",
    "kernel_deactivate_source_route": "fw_kernel_deactivate_source_route",
    "kernel_dont_accept_redirects": "fw_kernel_dont_accept_redirects",
}

# IPv6-only variables (from main_ipv6.conf)
MAIN_V6_ONLY = {
    "forward_private_ips": "fw_forward_private_ips_v6",
    "restrict_local_service_to_net": "fw_restrict_local_service_to_net_v6",
    "restrict_local_net_to_net": "fw_restrict_local_net_to_net_v6",
    "allow_ext_service": "fw_allow_ext_service_v6",
    "allow_ext_net": "fw_allow_ext_net_v6",
    "allow_local_service": "fw_allow_local_service_v6",
    "allow_local_service_from_networks": "fw_allow_local_service_from_networks_v6",
    "portforward_tcp": "fw_portforward_tcp_v6",
    "portforward_udp": "fw_portforward_udp_v6",
    "munin_remote_ip": "munin_remote_ipv6",
    "dovecot_auth_allowed_networks": "fw_dovecot_auth_allowed_networks_v6",
    "xmmp_remote_out_services": "fw_xmmp_remote_out_services_v6",
    "resolver_allowed_networks": "fw_resolver_allowed_networks_v6",
    "kernel_forward_between_interfaces": "fw_kernel_forward_between_interfaces",
}


# ---------------------------------------------------------------------------
# Parsing
# ---------------------------------------------------------------------------

def parse_bash_config(text):
    """
    Parse key=value pairs from a bash config file.
    Handles: var="value", var=value, var=true/false
    Multiline values (var="line1\n  line2\n") are joined as a single string.
    Returns dict of {varname: value_string}
    """
    result = {}
    warnings = []

    # Collapse multiline quoted strings: "...\n  ..." → "... ..."
    # Strategy: scan char by char for opening " after =, collect until closing "
    lines = text.splitlines()
    i = 0
    while i < len(lines):
        line = lines[i].strip()

        # Skip comments and blank lines
        if not line or line.startswith('#'):
            i += 1
            continue

        # Match assignment
        m = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)=(.*)', line)
        if not m:
            i += 1
            continue

        varname = m.group(1)
        rest = m.group(2).strip()

        # Quoted value (single or double quotes, may span multiple lines)
        if rest and rest[0] in ('"', "'"):
            quote_char = rest[0]
            collected = rest[1:]  # strip opening quote
            parts = []
            closed = False

            while True:
                close_pos = collected.find(quote_char)
                if close_pos != -1:
                    parts.append(collected[:close_pos])
                    # join all parts; split() collapses whitespace and drops empty lines
                    value = ' '.join(' '.join(parts).split())
                    result[varname] = value
                    closed = True
                    break
                else:
                    parts.append(collected)
                    i += 1
                    if i >= len(lines):
                        break
                    collected = lines[i].strip()

            if not closed:
                warnings.append(f"  # {varname}: unterminated quoted string — skipped")

        else:
            # Unquoted value (true, false, $var_ref, number, etc.)
            # Strip trailing comment
            value = re.sub(r'\s+#.*$', '', rest).strip()
            result[varname] = value

        i += 1

    return result, warnings


def ssh_cat(host, user, port, path, sudo_password=None):
    """Read a file from a remote host via SSH. Returns file content or None."""
    ssh_cmd = ["ssh"]
    if user:
        ssh_cmd += ["-l", user]
    if port:
        ssh_cmd += ["-p", str(port)]
    ssh_cmd += ["-o", "BatchMode=yes", "-o", "ConnectTimeout=10", host]

    if sudo_password is not None:
        # Use sudo -S to read password from stdin; -p '' suppresses the prompt
        ssh_cmd += [f"sudo -S -p '' cat {path}"]
        stdin_data = sudo_password + "\n"
    else:
        ssh_cmd += [f"cat {path}"]
        stdin_data = None

    try:
        result = subprocess.run(
            ssh_cmd, input=stdin_data, capture_output=True, text=True, timeout=30
        )
        if result.returncode != 0:
            print(f"  WARNING: could not read {path}: {result.stderr.strip()}", file=sys.stderr)
            return None
        return result.stdout
    except subprocess.TimeoutExpired:
        print(f"  ERROR: SSH timeout reading {path}", file=sys.stderr)
        return None


def coerce_bool(value):
    """Convert bash true/false string to Python bool, or return string."""
    if value.lower() in ("true", "yes", "1"):
        return True
    if value.lower() in ("false", "no", "0"):
        return False
    return value  # keep as string (e.g. $standard_ssh_port)


def yaml_value(v):
    """Format a Python value as a YAML-safe string."""
    if isinstance(v, bool):
        return "true" if v else "false"
    if v == "":
        return '""'
    # Quote if contains special YAML characters
    if any(c in str(v) for c in [':', '#', '{', '}', '[', ']', ',', '&', '*', '?', '|', '-', '<', '>', '=', '!', '%', '@', '`', '"', "'"]):
        # Use double-quote with escaping
        escaped = str(v).replace('\\', '\\\\').replace('"', '\\"')
        return f'"{escaped}"'
    return str(v)


def build_host_vars(parsed_iface_v4, parsed_iface_v6, parsed_main_v4, parsed_main_v6):
    """
    Map parsed bash variables to Ansible fw_* variables.
    Returns dict of {ansible_var: value} containing only non-default values.
    """
    result = {}

    # --- Interfaces: extract lists from numbered vars ---
    def extract_list(parsed, prefix, suffix="", count=3):
        items = []
        for i in range(1, count + 1):
            v = parsed.get(f"{prefix}{i}{suffix}", "").strip()
            if v:
                items.append(v)
        return items

    fw_ext_interfaces = extract_list(parsed_iface_v4, "ext_if_")
    fw_ext_ips_v4 = extract_list(parsed_iface_v4, "ext_", suffix="_ip")   # ext_1_ip, ext_2_ip, ext_3_ip
    fw_ext_ips_v6 = extract_list(parsed_iface_v6, "ext_", suffix="_ip")
    fw_local_interfaces = extract_list(parsed_iface_v4, "local_if_")
    fw_local_ips_v4 = extract_list(parsed_iface_v4, "local_", suffix="_ip")
    fw_local_ips_v6 = extract_list(parsed_iface_v6, "local_", suffix="_ip")
    fw_lxc_guest_ips_v4 = extract_list(parsed_iface_v4, "lxc_guest_", suffix="_ip", count=7)
    fw_lxc_guest_ips_v6 = extract_list(parsed_iface_v6, "lxc_guest_", suffix="_ip", count=7)

    if fw_ext_interfaces:
        result["fw_ext_interfaces"] = fw_ext_interfaces
    if fw_ext_ips_v4:
        result["fw_ext_ips_v4"] = fw_ext_ips_v4
    if fw_ext_ips_v6:
        result["fw_ext_ips_v6"] = fw_ext_ips_v6
    if fw_local_interfaces:
        result["fw_local_interfaces"] = fw_local_interfaces
    if fw_local_ips_v4:
        result["fw_local_ips_v4"] = fw_local_ips_v4
    if fw_local_ips_v6:
        result["fw_local_ips_v6"] = fw_local_ips_v6
    if fw_lxc_guest_ips_v4:
        result["fw_lxc_guest_ips_v4"] = fw_lxc_guest_ips_v4
    if fw_lxc_guest_ips_v6:
        result["fw_lxc_guest_ips_v6"] = fw_lxc_guest_ips_v6

    # vpn_ifs / wg_ifs / nat_devices (same in both interface files)
    for bash_var, ansible_var in [("vpn_ifs", "fw_vpn_ifs"), ("wg_ifs", "fw_wg_ifs"), ("nat_devices", "fw_nat_devices")]:
        v = parsed_iface_v4.get(bash_var, "")
        if v and v != DEFAULTS.get(ansible_var, ""):
            result[ansible_var] = v

    # --- Shared main variables (read from ipv4) ---
    for bash_var, ansible_var in MAIN_SHARED.items():
        raw = parsed_main_v4.get(bash_var)
        if raw is None:
            continue
        v = coerce_bool(raw) if raw.lower() in ("true", "false") else raw
        default = DEFAULTS.get(ansible_var)
        if v != default:
            result[ansible_var] = v

    # --- IPv4-only main variables ---
    for bash_var, ansible_var in MAIN_V4_ONLY.items():
        raw = parsed_main_v4.get(bash_var)
        if raw is None:
            continue
        v = coerce_bool(raw) if raw.lower() in ("true", "false") else raw
        default = DEFAULTS.get(ansible_var)
        if v != default:
            result[ansible_var] = v

    # --- IPv6-only main variables ---
    for bash_var, ansible_var in MAIN_V6_ONLY.items():
        raw = parsed_main_v6.get(bash_var)
        if raw is None:
            continue
        v = coerce_bool(raw) if raw.lower() in ("true", "false") else raw
        default = DEFAULTS.get(ansible_var)
        if v != default:
            result[ansible_var] = v

    return result


def render_yaml(hostname, host_vars, all_warnings):
    """Render the host_vars as YAML text."""
    lines = [
        "---",
        f"# ipt-firewall configuration for {hostname}",
        "# Generated by extract-fw-host-vars.py - review before committing!",
        "# Place in: host_vars/<hostname>/ipt_firewall.yml",
        "",
    ]

    lines.append("fw_manage_config: false")
    lines.append("")

    if all_warnings:
        lines.append("# WARNINGS — manual review needed:")
        for w in all_warnings:
            lines.append(w)
        lines.append("")

    # Group output by section
    sections = [
        ("Network", ["fw_ext_interfaces", "fw_ext_ips_v4", "fw_ext_ips_v6",
                     "fw_local_interfaces", "fw_local_ips_v4", "fw_local_ips_v6",
                     "fw_lxc_guest_ips_v4", "fw_lxc_guest_ips_v6",
                     "fw_vpn_ifs", "fw_wg_ifs", "fw_nat_devices"]),
        ("Munin", ["munin_remote_ipv4", "munin_remote_ipv6", "fw_munin_local_port",
                   "fw_munin_server_ips", "fw_forward_munin_server_ips", "fw_munin_remote_port"]),
        ("Bridged / LXC", ["fw_do_not_firewall_bridged_traffic", "fw_do_not_firewall_lx_guest_systems"]),
        ("Drop policies", ["fw_drop_icmp", "fw_drop_mndp", "fw_drop_mdns"]),
        ("Outgoing / interfaces", ["fw_allow_all_outgoing_traffic", "fw_blocked_ifs", "fw_unprotected_ifs"]),
        ("Forwarding", ["fw_forward_private_ips_v4", "fw_forward_private_ips_v6",
                        "fw_kernel_activate_forwarding", "fw_kernel_forward_between_interfaces"]),
        ("Access control IPv4", ["fw_restrict_local_service_to_net_v4", "fw_restrict_local_net_to_net_v4",
                                  "fw_allow_ext_service_v4", "fw_allow_ext_net_v4",
                                  "fw_allow_local_service_v4", "fw_allow_local_service_from_networks_v4"]),
        ("Access control IPv6", ["fw_restrict_local_service_to_net_v6", "fw_restrict_local_net_to_net_v6",
                                  "fw_allow_ext_service_v6", "fw_allow_ext_net_v6",
                                  "fw_allow_local_service_v6", "fw_allow_local_service_from_networks_v6"]),
        ("SSH", ["fw_ssh_server_ips", "fw_forward_ssh_server_ips", "fw_ssh_ports"]),
        ("HTTP", ["fw_http_server_ips", "fw_forward_http_server_ips", "fw_http_ports",
                  "fw_log_cgi_traffic_out", "fw_cgi_script_users"]),
        ("Mail", ["fw_smtpd_ips", "fw_forward_smtpd_ips", "fw_smtpd_additional_listen_ports",
                  "fw_smtpd_additional_outgoing_ports", "fw_mail_server_ips", "fw_forward_mail_server_ips",
                  "fw_mail_user_ports", "fw_mail_client_ips", "fw_forward_mail_client_ips",
                  "fw_dovecot_auth_service", "fw_dovecot_auth_port",
                  "fw_dovecot_auth_allowed_networks_v4", "fw_dovecot_auth_allowed_networks_v6"]),
        ("DNS", ["fw_dns_server_ips", "fw_forward_dns_server_ips",
                 "fw_local_resolver_service", "fw_resolver_port",
                 "fw_resolver_allowed_networks_v4", "fw_resolver_allowed_networks_v6"]),
        ("NTP", ["fw_local_ntp_service", "fw_ntp_port", "fw_ntp_allowed_net"]),
        ("DHCP", ["fw_dhcp_server_ifs", "fw_dhcp_client_ifs"]),
        ("VPN / WireGuard", ["fw_vpn_server_ips", "fw_forward_vpn_server_ips", "fw_vpn_ports",
                              "fw_wireguard_server_ips", "fw_forward_wireguard_server_ips",
                              "fw_wireguard_server_ports", "fw_wireguard_out_ports"]),
        ("FTP", ["fw_ftp_server_ips", "fw_forward_ftp_server_ips", "fw_ftp_passive_port_range"]),
        ("XMPP", ["fw_xmpp_server_ips", "fw_forward_xmpp_server_ips",
                  "fw_xmmp_tcp_in_ports", "fw_xmmp_tcp_out_ports",
                  "fw_xmmp_remote_out_services_v4", "fw_xmmp_remote_out_services_v6"]),
        ("Mumble", ["fw_mumble_server_ips", "fw_forward_mumble_server_ips", "fw_mumble_ports"]),
        ("Jitsi", ["fw_jitsi_server_ips", "fw_forward_jitsi_server_ips",
                   "fw_jitsi_tcp_ports", "fw_jitsi_udp_port_range",
                   "fw_jitsi_tcp_ports_out", "fw_jitsi_udp_ports_out",
                   "fw_jitsi_dovecot_auth", "fw_jitsi_dovecot_host",
                   "fw_jitsi_jibri_remote_auth", "fw_jitsi_jibri_remote_ips",
                   "fw_jibri_server_ips", "fw_forward_jibri_server_ips", "fw_jibri_remote_jitsi_server"]),
        ("TURN / STUN", ["fw_nc_turn_server_ips", "fw_forward_nc_turn_server_ips",
                          "fw_nc_turn_ports", "fw_nc_turn_udp_ports"]),
        ("Mattermost", ["fw_mm_server_ips", "fw_forward_mm_server_ips"]),
        ("Prometheus", ["fw_prometheus_local_server_ips", "fw_prometheus_local_client_ips",
                        "fw_prometheus_remote_server_ips"]),
        ("Xymon", ["fw_xymon_server_ips", "fw_local_xymon_client", "fw_xymon_port"]),
        ("Rsync", ["fw_rsync_out_ips", "fw_forward_rsync_out_ips", "fw_rsync_ports"]),
        ("Out ports", ["fw_tcp_out_ports", "fw_forward_tcp_out_ports",
                       "fw_udp_out_ports", "fw_forward_udp_out_ports"]),
        ("Portforwarding", ["fw_portforward_tcp_v4", "fw_portforward_udp_v4",
                             "fw_portforward_tcp_v6", "fw_portforward_udp_v6"]),
        ("Block", ["fw_blocked_ips", "fw_block_tcp_ports", "fw_block_udp_ports"]),
        ("Protection / limits", ["fw_protection_against_syn_flooding",
                                  "fw_protection_against_port_scanning",
                                  "fw_protection_against_ssh_brute_force_attacks",
                                  "fw_limit_connections_per_source_IP", "fw_per_IP_connection_limit",
                                  "fw_limit_new_tcp_connections_per_seconds_per_source_IP",
                                  "fw_limit_new_tcp_connections_per_seconds_ports"]),
        ("Kernel IPv4", ["fw_kernel_support_dynaddr", "fw_dynaddr_flag",
                          "fw_kernel_reduce_timeouts", "fw_kernel_tcp_syncookies",
                          "fw_kernel_protect_against_icmp_bogus_messages",
                          "fw_kernel_ignore_broadcast_ping",
                          "fw_kernel_deactivate_source_route", "fw_kernel_dont_accept_redirects",
                          "fw_kernel_activate_rp_filter", "fw_kernel_log_martians"]),
        ("Special", ["fw_create_traffic_counter", "fw_create_iperf_rules"]),
    ]

    emitted = set()
    for section_name, keys in sections:
        section_lines = []
        for k in keys:
            if k in host_vars:
                v = host_vars[k]
                if isinstance(v, list):
                    section_lines.append(f"{k}:")
                    for item in v:
                        section_lines.append(f"  - \"{item}\"")
                elif isinstance(v, bool):
                    section_lines.append(f"{k}: {'true' if v else 'false'}")
                else:
                    section_lines.append(f"{k}: {yaml_value(str(v))}")
                emitted.add(k)

        if section_lines:
            lines.append(f"# --- {section_name}")
            lines.extend(section_lines)
            lines.append("")

    # Anything not covered by sections
    remaining = {k: v for k, v in host_vars.items() if k not in emitted}
    if remaining:
        lines.append("# --- Other")
        for k, v in remaining.items():
            if isinstance(v, list):
                lines.append(f"{k}:")
                for item in v:
                    lines.append(f"  - \"{item}\"")
            elif isinstance(v, bool):
                lines.append(f"{k}: {'true' if v else 'false'}")
            else:
                lines.append(f"{k}: {yaml_value(str(v))}")
        lines.append("")

    return "\n".join(lines)


def main():
    parser = argparse.ArgumentParser(description="Extract ipt-firewall host_vars from a remote host")
    parser.add_argument("hostname", help="Target hostname (must be in SSH config or known_hosts)")
    parser.add_argument("--user", "-u", default="chris", help="SSH user (default: chris)")
    parser.add_argument("--port", "-p", type=int, default=None, help="SSH port (default: 22)")
    parser.add_argument("--output", "-o", default=None, help="Output file (default: stdout)")
    parser.add_argument("--sudo", "-s", action="store_true",
                        help="Read files via sudo (prompts for sudo password once)")
    parser.add_argument("--dry-run", action="store_true", help="Print SSH commands without executing")
    args = parser.parse_args()

    hostname = args.hostname
    conf_dir = "/etc/ipt-firewall"
    files = {
        "iface_v4": f"{conf_dir}/interfaces_ipv4.conf",
        "iface_v6": f"{conf_dir}/interfaces_ipv6.conf",
        "main_v4":  f"{conf_dir}/main_ipv4.conf",
        "main_v6":  f"{conf_dir}/main_ipv6.conf",
    }

    if args.dry_run:
        cmd = "sudo -S -p '' cat" if args.sudo else "cat"
        for key, path in files.items():
            print(f"ssh {args.user}@{hostname} {cmd} {path}")
        return

    sudo_password = None
    if args.sudo:
        import getpass
        sudo_password = getpass.getpass(f"sudo password for {args.user}@{hostname}: ")

    print(f"Connecting to {hostname} as {args.user} ...", file=sys.stderr)

    contents = {}
    for key, path in files.items():
        print(f"  Reading {path} ...", file=sys.stderr)
        content = ssh_cat(hostname, args.user, args.port, path, sudo_password=sudo_password)
        contents[key] = content or ""

    all_warnings = []
    parsed = {}
    for key, text in contents.items():
        p, warnings = parse_bash_config(text)
        parsed[key] = p
        if warnings:
            all_warnings.extend([f"  # [{key}] {w}" for w in warnings])

    host_vars = build_host_vars(
        parsed["iface_v4"], parsed["iface_v6"],
        parsed["main_v4"],  parsed["main_v6"],
    )

    yaml_text = render_yaml(hostname, host_vars, all_warnings)

    if args.output:
        out_path = Path(args.output)
        out_path.parent.mkdir(parents=True, exist_ok=True)
        out_path.write_text(yaml_text)
        print(f"Written to {out_path}", file=sys.stderr)
    else:
        print(yaml_text)


if __name__ == "__main__":
    main()
