# Author: Antti Myyrä <antti.myyra@upcloud.com>
#
# This file is part of cloud-init. See LICENSE file for license information.

import json

from cloudinit import dmi
from cloudinit import log as logging
from cloudinit import net as cloudnet
from cloudinit import url_helper

LOG = logging.getLogger(__name__)


def convert_to_network_config_v1(config):
    """
    Convert the UpCloud network metadata description into
    Cloud-init's version 1 netconfig format.

    Example JSON:
    {
      "interfaces": [
        {
          "index": 1,
          "ip_addresses": [
            {
              "address": "94.237.105.53",
              "dhcp": true,
              "dns": [
                "94.237.127.9",
                "94.237.40.9"
              ],
              "family": "IPv4",
              "floating": false,
              "gateway": "94.237.104.1",
              "network": "94.237.104.0/22"
            },
            {
              "address": "94.237.105.50",
              "dhcp": false,
              "dns": [],
              "family": "IPv4",
              "floating": true,
              "gateway": "",
              "network": "94.237.105.50/32"
            }
          ],
          "mac": "32:d5:ba:4a:36:e7",
          "network_id": "031457f4-0f8c-483c-96f2-eccede02909c",
          "type": "public"
        },
        {
          "index": 2,
          "ip_addresses": [
            {
              "address": "10.6.3.27",
              "dhcp": true,
              "dns": [],
              "family": "IPv4",
              "floating": false,
              "gateway": "10.6.0.1",
              "network": "10.6.0.0/22"
            }
          ],
          "mac": "32:d5:ba:4a:84:cc",
          "network_id": "03d82553-5bea-4132-b29a-e1cf67ec2dd1",
          "type": "utility"
        },
        {
          "index": 3,
          "ip_addresses": [
            {
              "address": "2a04:3545:1000:720:38d6:baff:fe4a:63e7",
              "dhcp": true,
              "dns": [
                "2a04:3540:53::1",
                "2a04:3544:53::1"
              ],
              "family": "IPv6",
              "floating": false,
              "gateway": "2a04:3545:1000:720::1",
              "network": "2a04:3545:1000:720::/64"
            }
          ],
          "mac": "32:d5:ba:4a:63:e7",
          "network_id": "03000000-0000-4000-8046-000000000000",
          "type": "public"
        },
        {
          "index": 4,
          "ip_addresses": [
            {
              "address": "172.30.1.10",
              "dhcp": true,
              "dns": [],
              "family": "IPv4",
              "floating": false,
              "gateway": "172.30.1.1",
              "network": "172.30.1.0/24"
            }
          ],
          "mac": "32:d5:ba:4a:8a:e1",
          "network_id": "035a0a4a-77b4-4de5-820d-189fc8135714",
          "type": "private"
        }
      ],
      "dns": [
        "94.237.127.9",
        "94.237.40.9"
      ]
    }
    """

    def _get_subnet_config(ip_addr, dns):
        if ip_addr.get("dhcp"):
            dhcp_type = "dhcp"
            if ip_addr.get("family") == "IPv6":
                # UpCloud currently passes IPv6 addresses via
                # StateLess Address Auto Configuration (SLAAC)
                dhcp_type = "ipv6_dhcpv6-stateless"
            return {"type": dhcp_type}

        static_type = "static"
        if ip_addr.get("family") == "IPv6":
            static_type = "static6"
        subpart = {
            "type": static_type,
            "control": "auto",
            "address": ip_addr.get("address"),
        }

        if ip_addr.get("gateway"):
            subpart["gateway"] = ip_addr.get("gateway")

        if "/" in ip_addr.get("network"):
            subpart["netmask"] = ip_addr.get("network").split("/")[1]

        if dns != ip_addr.get("dns") and ip_addr.get("dns"):
            subpart["dns_nameservers"] = ip_addr.get("dns")

        return subpart

    nic_configs = []
    macs_to_interfaces = cloudnet.get_interfaces_by_mac()
    LOG.debug("NIC mapping: %s", macs_to_interfaces)

    for raw_iface in config.get("interfaces"):
        LOG.debug("Considering %s", raw_iface)

        mac_address = raw_iface.get("mac")
        if mac_address not in macs_to_interfaces:
            raise RuntimeError(
                "Did not find network interface on system "
                "with mac '%s'. Cannot apply configuration: %s"
                % (mac_address, raw_iface)
            )

        iface_type = raw_iface.get("type")
        sysfs_name = macs_to_interfaces.get(mac_address)

        LOG.debug(
            "Found %s interface '%s' with address '%s' (index %d)",
            iface_type,
            sysfs_name,
            mac_address,
            raw_iface.get("index"),
        )

        interface = {
            "type": "physical",
            "name": sysfs_name,
            "mac_address": mac_address
        }

        subnets = []
        for ip_address in raw_iface.get("ip_addresses"):
            sub_part = _get_subnet_config(ip_address, config.get("dns"))
            subnets.append(sub_part)

        interface["subnets"] = subnets
        nic_configs.append(interface)

    if config.get("dns"):
        LOG.debug("Setting DNS nameservers to %s", config.get("dns"))
        nic_configs.append({
            "type": "nameserver",
            "address": config.get("dns")
        })

    return {"version": 1, "config": nic_configs}


def convert_network_config(config):
    return convert_to_network_config_v1(config)


def read_metadata(url, timeout=2, sec_between=2, retries=30):
    response = url_helper.readurl(
        url, timeout=timeout, sec_between=sec_between, retries=retries
    )
    if not response.ok():
        raise RuntimeError("unable to read metadata at %s" % url)
    return json.loads(response.contents.decode())


def read_sysinfo():
    # UpCloud embeds vendor ID and server UUID in the
    # SMBIOS information

    # Detect if we are on UpCloud and return the UUID

    vendor_name = dmi.read_dmi_data("system-manufacturer")
    if vendor_name != "UpCloud":
        return False, None

    server_uuid = dmi.read_dmi_data("system-uuid")
    if server_uuid:
        LOG.debug(
            "system identified via SMBIOS as UpCloud server: %s",
            server_uuid
        )
    else:
        msg = (
            "system identified via SMBIOS as a UpCloud server, but "
            "did not provide an ID. Please contact support via"
            "https://hub.upcloud.com or via email with support@upcloud.com"
        )
        LOG.critical(msg)
        raise RuntimeError(msg)

    return True, server_uuid
