# frozen_string_literal: true

module Facter
  module Resolvers
    class NetworkingLinux < BaseResolver
      @semaphore = Mutex.new
      @fact_list = {}
      DIRS = ['/var/lib/dhclient/', '/var/lib/dhcp/', '/var/lib/dhcp3/', '/var/lib/NetworkManager/', '/var/db/'].freeze

      class << self
        private

        def post_resolve(fact_name)
          log.debug('in networking_linux_resolver')
          @fact_list.fetch(fact_name) { retrieve_network_info(fact_name) }

          @fact_list[fact_name]
        end

        def retrieve_network_info(fact_name)
          @fact_list ||= {}

          retrieve_interface_info
          retrieve_interfaces_mac_and_mtu
          retrieve_default_interface
          ::Resolvers::Utils::Networking.expand_main_bindings(@fact_list)
          @fact_list[fact_name]
        end

        def retrieve_interfaces_mac_and_mtu
          @fact_list[:interfaces].map do |name, info|
            macaddress = Util::FileHelper.safe_read("/sys/class/net/#{name}/address", nil)
            info[:mac] = macaddress.strip if macaddress && !macaddress.include?('00:00:00:00:00:00')
            mtu = Util::FileHelper.safe_read("/sys/class/net/#{name}/mtu", nil)
            info[:mtu] = mtu.strip.to_i if mtu
          end
        end

        def retrieve_interface_info
          log.debug('retrieve_interface_info')
          output = Facter::Core::Execution.execute('ip -o address', logger: log)
          log.debug("ip -o address result is:\n#{output}")

          interfaces = {}

          output.each_line do |ip_line|
            ip_tokens = ip_line.split(' ')

            log.debug("ip_tokens = #{ip_tokens}")
            log.debug("interfaces = #{interfaces}")
            fill_ip_v4_info!(ip_tokens, interfaces)
            fill_io_v6_info!(ip_tokens, interfaces)
            find_dhcp!(ip_tokens, interfaces)
          end

          @fact_list[:interfaces] = interfaces
        end

        def find_dhcp!(tokens, network_info)
          interface_name = tokens[1]
          return if !network_info[interface_name] || network_info[interface_name][:dhcp]

          index = tokens[0].delete(':')
          dhcp = Util::FileHelper.safe_read("/run/systemd/netif/leases/#{index}", nil)
          network_info[interface_name][:dhcp] = dhcp.match(/SERVER_ADDRESS=(.*)/)[1] if dhcp
          network_info[interface_name][:dhcp] ||= retrieve_from_other_directories(interface_name)
        end

        def retrieve_from_other_directories(interface_name)
          DIRS.each do |dir|
            next unless File.readable?(dir)

            lease_files = Dir.entries(dir).select { |file| file =~ /dhclient.*\.lease/ }
            next if lease_files.empty?

            lease_files.select do |file|
              content = Util::FileHelper.safe_read("#{dir}#{file}", nil)
              next unless content =~ /interface.*#{interface_name}/

              return content.match(/dhcp-server-identifier ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/)[1]
            end
          end
          return unless File.readable?('/var/lib/NetworkManager/')

          search_internal_leases(interface_name)
        end

        def search_internal_leases(interface_name)
          files = Dir.entries('/var/lib/NetworkManager/').reject { |dir| dir =~ /^\.+$/ }
          lease_file = files.find { |file| file =~ /internal.*#{interface_name}\.lease/ }
          return unless lease_file

          dhcp = Util::FileHelper.safe_read("/var/lib/NetworkManager/#{lease_file}", nil)

          return unless dhcp

          dhcp = dhcp.match(/SERVER_ADDRESS=(.*)/)
          dhcp[1] if dhcp
        end

        def fill_ip_v4_info!(ip_tokens, network_info)
          log.debug('fill_ip_v4_info!')
          return unless ip_tokens[2].casecmp('inet').zero?

          interface_name, ip4_address, ip4_mask_length = retrieve_name_and_ip_info(ip_tokens)

          log.debug("interface_name = #{interface_name}\n" \
                      "ip4_address = #{ip4_address}\n" \
                      "ip4_mask_length = #{ip4_mask_length}")

          binding = ::Resolvers::Utils::Networking.build_binding(ip4_address, ip4_mask_length)
          build_network_info_structure!(network_info, interface_name, :bindings)

          network_info[interface_name][:bindings] << binding
        end

        def retrieve_name_and_ip_info(tokens)
          interface_name = tokens[1]
          ip_info = tokens[3].split('/')
          ip_address = ip_info[0]
          ip_mask_length = ip_info[1]

          [interface_name, ip_address, ip_mask_length]
        end

        def fill_io_v6_info!(ip_tokens, network_info)
          return unless ip_tokens[2].casecmp('inet6').zero?

          interface_name, ip6_address, ip6_mask_length = retrieve_name_and_ip_info(ip_tokens)

          binding = ::Resolvers::Utils::Networking.build_binding(ip6_address, ip6_mask_length)

          build_network_info_structure!(network_info, interface_name, :bindings6)

          network_info[interface_name][:scope6] ||= ip_tokens[5]
          network_info[interface_name][:bindings6] << binding
        end

        def retrieve_default_interface
          output = Facter::Core::Execution.execute('ip route get 1', logger: log)

          ip_route_tokens = output.each_line.first.strip.split(' ')
          default_interface = ip_route_tokens[4]

          @fact_list[:primary_interface] = default_interface
        end

        def build_network_info_structure!(network_info, interface_name, binding)
          network_info[interface_name] = {} unless network_info.dig(interface_name)
          network_info[interface_name][binding] = [] unless network_info.dig(interface_name, binding)
        end
      end
    end
  end
end