# frozen_string_literal: true

module Facter
  module Resolvers
    class Networking < BaseResolver
      init_resolver

      class << self
        private

        def post_resolve(fact_name, _options)
          @fact_list.fetch(fact_name) { read_facts(fact_name) }
        end

        def read_facts(fact_name)
          interfaces_data
          primary_interface
          Facter::Util::Resolvers::Networking.expand_main_bindings(@fact_list)
          @fact_list[fact_name]
        end

        def primary_interface
          primary_helper = Facter::Util::Resolvers::Networking::PrimaryInterface
          primary_interface ||= primary_helper.read_from_route

          primary_interface ||= primary_helper.find_in_interfaces(@fact_list[:interfaces])

          @fact_list[:primary_interface] = primary_interface
        end

        def interfaces_data
          command_response = Facter::Core::Execution.execute('ifconfig -a', logger: log)

          clean_up_interfaces_response(command_response)
          parse_interfaces_response(command_response)
        end

        def clean_up_interfaces_response(response)
          # convert ip ranges into single ip. eg. 10.16.132.213 -->  10.16.132.213 is converted to 10.16.132.213
          # convert ip6 ranges into single ip. eg. 2001:db8:cafe::132:213 -->
          # 2001:db8:cafe::132:213 is converted to 2001:db8:cafe::132:213
          response.gsub!(/([\da-fA-F]+([.:]+[\da-fA-F]+)*)\s+-->\s+[\da-fA-F]+([.:]+[\da-fA-F]+)*/, '\\1')
        end

        def parse_interfaces_response(response)
          parsed_interfaces_data = {}
          interfaces_data = Hash[*response.split(/^([A-Za-z0-9_.]+): /)[1..-1]]

          interfaces_data.each do |interface_name, raw_data|
            parsed_interface_data = {}

            extract_mtu(raw_data, parsed_interface_data)
            extract_mac(raw_data, parsed_interface_data)
            extract_dhcp(interface_name, raw_data, parsed_interface_data)
            extract_ip_data(raw_data, parsed_interface_data)

            parsed_interfaces_data[interface_name] = parsed_interface_data
          end
          @fact_list[:interfaces] = parsed_interfaces_data unless parsed_interfaces_data.empty?
        end

        def extract_mtu(raw_data, parsed_interface_data)
          mtu = raw_data.match(/mtu\s+(\d+)/)&.captures&.first&.to_i
          parsed_interface_data[:mtu] = mtu unless mtu.nil?
        end

        def extract_mac(raw_data, parsed_interface_data)
          mac = raw_data.match(/(?:ether|lladdr)\s+((?:\w?\w:){5}\w?\w)|(?:infiniband)\s+((?:\w?\w:){19}\w?\w)/)
                        &.captures&.compact&.first
          parsed_interface_data[:mac] = mac unless mac.nil?
        end

        def extract_dhcp(interface_name, raw_data, parsed_interface_data)
          return unless /status:\s+active/.match?(raw_data)

          result = Facter::Core::Execution.execute("ipconfig getoption #{interface_name} " \
                                                     'server_identifier', logger: log)

          parsed_interface_data[:dhcp] = result.match(/^[\d.a-f:\s]+$/)&.to_s&.strip unless result.empty?
        end

        def extract_ip_data(raw_data, parsed_interface_data)
          ip = extract_values(raw_data, /inet (\S+)/)
          mask = extract_values(raw_data, /netmask (\S+)/).map { |val| val.hex.to_s(2).count('1') }

          ip6 = extract_values(raw_data, /inet6 (\S+)/).map { |val| val.gsub(/%.+/, '') }
          mask6 = extract_values(raw_data, /prefixlen (\S+)/)

          parsed_interface_data[:bindings] = create_bindings(ip, mask) unless ip.empty? or mask.empty?
          parsed_interface_data[:bindings6] = create_bindings(ip6, mask6) unless ip6.empty?
        end

        def extract_values(data, regex)
          results = []
          data.scan(regex).flatten.each do |val|
            results << val
          end
          results
        end

        def create_bindings(ips, masks)
          bindings = []
          ips.zip(masks).each do |ip, mask|
            bindings << Facter::Util::Resolvers::Networking.build_binding(ip, mask)
          end
          bindings
        end
      end
    end
  end
end
