#!/usr/bin/python3
"""Write a candidate vpn-router.json to the path given on the command line.

Called from postinst, which hands the result to ucf(1). ucf compares it with
/etc/vpn-router/vpn-router.json and decides what to do about local changes, so
this script never writes to /etc and never has to worry about destroying an
administrator's edits.

The candidate is the current /etc/vpn-router/vpn-router.json as it is, or the
shipped defaults when there is none, with every debconf answer that was given
written over it. Without an answer that changes it, the candidate is the current
file byte for byte, so
an upgrade or a reconfiguration that changes nothing gives ucf nothing to ask;
defaults the file leaves out are applied by vpnrouter.load_config() when the
file is read. An empty answer means "leave alone", which is why clearing the
pre-shared key from the debconf database after installation does not blank it
on the next dpkg-reconfigure.

Answers arrive as VPN_ROUTER_* environment variables and are converted to the
type of the setting they fill: comma-separated lists become arrays, "true" and
"false" become booleans and digits become integers.
"""

import json
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))

import vpnrouter

# environment variable -> (section, option)
ANSWERS = {
    'VPN_ROUTER_PLATFORM':        ('general', 'platform'),
    'VPN_ROUTER_MODE':            ('general', 'mode'),
    'VPN_ROUTER_INT_ADDR':        ('local', 'int_addr'),
    'VPN_ROUTER_EXTERNAL_INTERFACE': ('interfaces', 'external'),
    'VPN_ROUTER_INTERNAL_INTERFACE': ('interfaces', 'internal'),
    'VPN_ROUTER_LOCAL_FQDN':      ('wan', 'local_fqdn'),
    'VPN_ROUTER_LOCAL_ID_MODE':   ('wan', 'local_id_mode'),
    'VPN_ROUTER_LOCAL_CIDRS':     ('local', 'cidrs'),
    'VPN_ROUTER_INT_GATEWAY_IP':  ('local', 'int_gateway_ip'),
    'VPN_ROUTER_REMOTE_ADDRS':    ('remote', 'addrs'),
    'VPN_ROUTER_REMOTE_ID':       ('remote', 'id'),
    'VPN_ROUTER_REMOTE_CIDRS':    ('remote', 'cidrs'),
    'VPN_ROUTER_PSK':             ('remote', 'psk'),
    'VPN_ROUTER_P2S_ENABLED':     ('p2s', 'enabled'),
    'VPN_ROUTER_P2S_ADDRESS_POOL': ('p2s', 'address_pool'),
    'VPN_ROUTER_P2S_CA_NAME':     ('p2s', 'ca_name'),
    'VPN_ROUTER_WG_ENABLED':      ('wireguard', 'enabled'),
    'VPN_ROUTER_WG_ADDRESS':      ('wireguard', 'address'),
    'VPN_ROUTER_WG_LISTEN_PORT':  ('wireguard', 'listen_port'),
}


def main():
    if len(sys.argv) != 2:
        print('usage: generate-config OUTPUT', file=sys.stderr)
        return 2
    output = sys.argv[1]

    try:
        current = vpnrouter.CONFIG_FILE.read_text(encoding='utf-8')
    except FileNotFoundError:
        current = None
    if current is None:
        cfg = vpnrouter.load_config()
    else:
        try:
            cfg = json.loads(current)
        except ValueError as exc:
            vpnrouter.fail(f'cannot parse {vpnrouter.CONFIG_FILE}: {exc}')
        if not isinstance(cfg, dict):
            vpnrouter.fail(f'cannot parse {vpnrouter.CONFIG_FILE}: the top level is not an object')

    answered = False
    for variable, (section, option) in ANSWERS.items():
        value = os.environ.get(variable)
        if not value:
            continue
        # A value that does not convert is kept as a string for validate() to report.
        default = vpnrouter.SCHEMA[section][option]
        if isinstance(default, bool):
            value = value == 'true'
        elif isinstance(default, int):
            value = int(value) if value.isdigit() else value
        elif isinstance(default, list):
            value = [item.strip() for item in value.split(',') if item.strip()]
        if not isinstance(cfg.get(section, {}), dict):
            vpnrouter.fail(f'cannot parse {vpnrouter.CONFIG_FILE}: {section} is not an object')
        if cfg.setdefault(section, {}).get(option) != value:
            cfg[section][option] = value
            answered = True

    # Without a change the candidate is the current file byte for byte, as ucf compares checksums.
    with open(output, 'w', encoding='utf-8') as handle:
        if current is not None and not answered:
            handle.write(current)
        else:
            json.dump(cfg, handle, indent=2)
            handle.write('\n')
    os.chmod(output, 0o600)
    return 0


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