# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

require "tempfile"
require "yaml"

require_relative "../../../../lib/vagrant/util/template_renderer"

module VagrantPlugins
  module GuestCoreOS
    module Cap
      class ConfigureNetworks
        extend Vagrant::Util::GuestInspection::Linux

        NETWORK_MANAGER_CONN_DIR = "/etc/NetworkManager/system-connections".freeze
        DEFAULT_ENVIRONMENT_IP = "127.0.0.1".freeze

        def self.configure_networks(machine, networks)
          comm = machine.communicate
          return configure_networks_cloud_init(machine, networks) if comm.test("command -v cloud-init")

          interfaces = machine.guest.capability(:network_interfaces)
          nm_dev = {}
          comm.execute("nmcli -t c show") do |type, data|
            if type == :stdout
              _, id, _, dev = data.strip.split(":")
              nm_dev[dev] = id
            end
          end
          comm.sudo("rm #{File.join(NETWORK_MANAGER_CONN_DIR, 'vagrant-*.conf')}",
            error_check: false)

          networks.each_with_index do |network, i|
            network[:device] = interfaces[network[:interface]]
            addr = IPAddr.new(network[:ip])
            mask = addr.mask(network[:netmask])
            if !network[:mac_address]
              comm.execute("cat /sys/class/net/#{network[:device]}/address") do |type, data|
                if type == :stdout
                  network[:mac_address] = data
                end
              end
            end

            f = Tempfile.new("vagrant-coreos-network")
            {
              connection: {
                type: "ethernet",
                id: network[:device],
                "interface-name": network[:device]
              },
              ethernet: {
                "mac-address": network[:mac_address]
              },
              ipv4: {
                method: "manual",
                addresses: "#{network[:ip]}/#{mask.prefix}",
                gateway: network.fetch(:gateway, mask.to_range.first.succ),
              },
            }.each_pair do |section, content|
              f.puts "[#{section}]"
              content.each_pair do |key, value|
                f.puts "#{key}=#{value}"
              end
            end
            f.close
            comm.sudo("nmcli d disconnect '#{network[:device]}'", error_check: false)
            comm.sudo("nmcli c delete '#{nm_dev[network[:device]]}'", error_check: false)
            dst = File.join("/var/tmp", "vagrant-#{network[:device]}.conf")
            final = File.join(NETWORK_MANAGER_CONN_DIR, "vagrant-#{network[:device]}.conf")
            comm.upload(f.path, dst)
            comm.sudo("chown root:root '#{dst}'")
            comm.sudo("chmod 0600 '#{dst}'")
            comm.sudo("mv '#{dst}' '#{final}'")
            comm.sudo("nmcli c load '#{final}'")
            comm.sudo("nmcli d connect '#{network[:device]}'")
            f.delete
          end
        end

        def self.configure_networks_cloud_init(machine, networks)
          cloud_config = {}
          # Locate configured IP addresses to drop in /etc/environment
          # for export. If no addresses found, fall back to default
          public_ip = catch(:public_ip) do
            machine.config.vm.networks.each do |type, opts|
              next if type != :public_network
              throw(:public_ip, opts[:ip]) if opts[:ip]
            end
            DEFAULT_ENVIRONMENT_IP
          end
          private_ip = catch(:private_ip) do
            machine.config.vm.networks.each do |type, opts|
              next if type != :private_network
              throw(:private_ip, opts[:ip]) if opts[:ip]
            end
            public_ip
          end
          cloud_config["write_files"] = [
            {"path" => "/etc/environment",
              "content" => "COREOS_PUBLIC_IPV4=#{public_ip}\nCOREOS_PRIVATE_IPV4=#{private_ip}"}
          ]

          # Generate configuration for any static network interfaces
          # which have been defined
          interfaces = machine.guest.capability(:network_interfaces)
          units = networks.map do |network|
            iface = network[:interface].to_i
            unit_name = "50-vagrant#{iface}.network"
            device = interfaces[iface]
            if network[:type].to_s == "dhcp"
              network_content = "DHCP=yes"
            else
              prefix = IPAddr.new("255.255.255.255/#{network[:netmask]}").to_i.to_s(2).count("1")
              address = "#{network[:ip]}/#{prefix}"
              network_content = "Address=#{address}"
            end
            {"name" => unit_name,
              "runtime" => "no",
              "content" => "[Match]\nName=#{device}\n[Network]\n#{network_content}"}
          end
          cloud_config["coreos"] = {"units" => units.compact}

          # Upload configuration and apply
          file = Tempfile.new("vagrant-coreos-networks")
          file.puts("#cloud-config\n")
          file.puts(cloud_config.to_yaml)
          file.close

          dst = "/var/tmp/networks.yml"
          svc_path = dst.tr("/", "-")[1..-1]
          machine.communicate.upload(file.path, dst)
          machine.communicate.sudo("systemctl start system-cloudinit@#{svc_path}.service")
        end
      end
    end
  end
end
