#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>

#ifdef WIRELESS
#include <iwlib.h>
#endif

#include <debian-installer.h>

#include "netplan-conf.h"

/* Functions for printing information in Netplan format. */

static void netplan_write_header(FILE *fd)
{
    // Consider using libnetplan.so instead of writing plain YAML.
    fprintf(fd, "# This is the network config written by debian-installer.\n");
    fprintf(fd, "# For more information, see netplan(5).\n");
    fprintf(fd, "network:\n");
    fprintf(fd, "  version: 2\n");
}

static void netplan_write_optional(FILE *fd, const struct netcfg_interface *interface)
{
    if (iface_is_hotpluggable(interface->name) || find_in_stab(interface->name)) {
        fprintf(fd, "      optional: true\n");
    }
}

static void netplan_write_wireless(FILE *fd, const struct netcfg_interface *interface)
{
    if (!(interface->essid && *interface->essid)) {
        di_error("Unspecified SSID"); // "essid=any" not supported by Netplan
        exit(1);
    }

    fprintf(fd, "  wifis:\n");
    fprintf(fd, "    %s:\n", interface->parentif ? interface->parentif : interface->name); // consider matching on mac address
    fprintf(fd, "      access-points:\n");
    fprintf(fd, "        \"%s\":\n", interface->essid);
    if (interface->wifi_security == REPLY_WPA || interface->wpa_supplicant_status == WPA_QUEUED)
        fprintf(fd, "          password: %s\n", interface->passphrase);
    else {
		fprintf(fd, "          auth:\n");
		fprintf(fd, "            key-management: none\n"); // open network
	}
    if (interface->mode == ADHOC)
        fprintf(fd, "          mode: adhoc\n");

    netplan_write_optional(fd, interface);
}

static void netplan_write_wired(FILE *fd, const struct netcfg_interface *interface)
{
    fprintf(fd, "  ethernets:\n");
    fprintf(fd, "    %s:\n", interface->parentif ? interface->parentif : interface->name); // consider matching on mac address

    netplan_write_optional(fd, interface);
}

static void netplan_write_common(FILE *fd, const struct netcfg_interface *interface)
{
    // DNS: nameservers and search domain
    if (!empty_str(interface->nameservers[0]) || !empty_str(domain)) {
        fprintf(fd, "      nameservers:\n");

        unsigned int i = 0;
        if (!empty_str(interface->nameservers[0])) {
            fprintf(fd, "        addresses:\n");
            for (i = 0; i < NETCFG_NAMESERVERS_MAX; i++) {
                if (!empty_str(interface->nameservers[i]))
                    fprintf(fd, "          - %s\n", interface->nameservers[i]);
            }
        }
        if (!empty_str(domain)) {
            fprintf(fd, "        search:\n");
            fprintf(fd, "          - %s\n", domain);
        }
    }

    // DHCP
    if (interface->dhcp == 1) {
        di_debug("Writing DHCP stanza for %s", interface->name);
        fprintf(fd, "      ipv6-privacy: true\n");
        fprintf(fd, "      dhcp4: true\n");
        fprintf(fd, "      dhcp6: true\n");
        if (!empty_str(interface->dhcp_hostname)) {
            fprintf(fd, "      dhcp4-overrides:\n");
            fprintf(fd, "        hostname: \"%s\"\n", interface->dhcp_hostname);
            fprintf(fd, "      dhcp6-overrides:\n");
            fprintf(fd, "        hostname: \"%s\"\n", interface->dhcp_hostname);
        }
    }

    // SLAAC
    if (interface->slaac == 1) {
        di_debug("Writing SLAAC stanza for %s", interface->name);
        // Implicitly enable Netplan's dhcp6 & ipv6-privacy, see:
        // https://netplan.rtfd.io/en/stable/netplan-yaml/#properties-for-all-device-types
        if (interface->dhcp == 0) {
            fprintf(fd, "      ipv6-privacy: true\n");
            fprintf(fd, "      dhcp6: true\n");
        }
        fprintf(fd, "      accept-ra: true\n");
    }

    // Static IP
    // IPv6 is unsupported on point-to-point links, according to
    // "netcfg-static.templates", so we can hardcode a /32 netmask in that case.
    if (interface->address_family == AF_INET || interface->address_family == AF_INET6) {
        di_debug("Writing static IP stanza for %s", interface->name);
        fprintf(fd, "      addresses:\n");
        fprintf(fd, "        - \"%s/%i\"\n", interface->ipaddress,
            empty_str(interface->pointopoint) ? interface->masklen : 32);
    }

    // Default gateway
    // Installing an "on-link" route for point-to-point or manual IPv4 /32 or
    // IPv6 /128 netmasks. See: https://salsa.debian.org/installer-team/netcfg/-/merge_requests/10
    if (!empty_str(interface->gateway) || !empty_str(interface->pointopoint)) {
        fprintf(fd, "      routes:\n");
        fprintf(fd, "        - to: default\n");
        fprintf(fd, "          via: %s\n",
            empty_str(interface->pointopoint) ? interface->gateway : interface->pointopoint);
        if (!empty_str(interface->pointopoint) ||
            (interface->address_family == AF_INET && interface->masklen == 32) ||
            (interface->address_family == AF_INET6 && interface->masklen == 128))
            fprintf(fd, "          on-link: true\n");
    }
}

static void netplan_write_vlan(FILE *fd, const struct netcfg_interface *interface)
{
    di_debug("Writing VLAN configuration for %s", interface->name);
    fprintf(fd, "  vlans:\n");
    fprintf(fd, "    vlan%d:\n", interface->vlanid);
    fprintf(fd, "      id: %d\n", interface->vlanid);
    fprintf(fd, "      link: %s\n", interface->parentif);
}

/* Write Netplan config file. */
void netplan_write_configuration(const struct netcfg_interface *interface)
{
    FILE    *config_file = NULL;
    char    buffer[NETPLAN_MAX_LEN_BUF] = {0};
    int     is_loopback = (interface->loopback == 1);
    int     is_wireless = is_wireless_iface(interface->name);

    // WEP is deprecated and not supported by Netplan. Do not write any configuration.
    if (is_wireless && interface->wepkey != NULL) {
        di_warning("Netplan does not support insecure WEP wireless networks.");
        di_debug("Not writing any Netplan configuration.");
        return;
    }

    /* Create the directory for the config file and clear any possible
     * previous files found there. */
    sprintf(buffer, "mkdir -p %s", NETPLAN_CONFIG_FILE_PATH);
    di_exec_shell(buffer);

    /* If the directory exist mkdir will do nothing, so just remove every file
     * there. Rely on the fact that for now netcfg only does config for one
     * interface. */
    sprintf(buffer, "rm %s/*", NETPLAN_CONFIG_FILE_PATH);
    di_exec_shell(buffer);

    /* Open file using its full path. */
    sprintf(buffer, "%s/%s", NETPLAN_CONFIG_FILE_PATH, NETPLAN_INSTALLER_FILE);
    config_file = fopen(buffer, "w");

    if (config_file == NULL) {
        di_info("Unable to open file for writing Netplan configuration: "
                "%s", strerror(errno));
        return;
    }

    if (fchmod(fileno(config_file), 0600) != 0) {
        di_error("Netplan configuration file cannot be protected "
                 "from reading: %s", strerror(errno));
        exit(1);
    }

    netplan_write_header(config_file);
    // Do not write anything, as loopback is set up by systemd implicitly.
    // Being a dependency, systemd is always available when Netplan is in use.
    if (is_loopback) {}
    else if (is_wireless)
        netplan_write_wireless(config_file, interface);
    else {
        netplan_write_wired(config_file, interface);
        if (interface->vlanid > 0)
            netplan_write_vlan(config_file, interface);
    }
    netplan_write_common(config_file, interface);

    fclose(config_file);
}
