# frozen_string_literal: true

module Awspec::Type
  # rubocop:disable Metrics/ClassLength
  class SecurityGroup < ResourceBase
    aws_resource Aws::EC2::SecurityGroup
    tags_allowed

    def resource_via_client
      @resource_via_client ||= find_security_group(@display_name)
    end

    def id
      @id ||= resource_via_client.group_id if resource_via_client
    end

    def opened?(port = nil, protocol = nil, cidr = nil)
      return inbound_opened?(port, protocol, cidr) if @inbound

      outbound_opened?(port, protocol, cidr)
    end

    def opened_only?(port = nil, protocol = nil, cidr = nil)
      return inbound_opened_only?(port, protocol, cidr) if @inbound

      outbound_opened_only?(port, protocol, cidr)
    end

    def inbound_opened?(port = nil, protocol = nil, cidr = nil)
      resource_via_client.ip_permissions.find do |permission|
        cidr_opened?(permission, cidr) && protocol_opened?(permission, protocol) && port_opened?(permission, port)
      end
    end

    def inbound_opened_only?(port = nil, protocol = nil, cidr = nil)
      permissions = resource_via_client.ip_permissions.select do |permission|
        protocol_opened?(permission, protocol) && port_opened?(permission, port)
      end
      cidrs = []
      permissions.each do |permission|
        permission.ip_ranges.select { |ip_range| cidrs.push(ip_range.cidr_ip) }
      end
      cidrs == Array(cidr)
    end

    def outbound_opened?(port = nil, protocol = nil, cidr = nil)
      resource_via_client.ip_permissions_egress.find do |permission|
        cidr_opened?(permission, cidr) && protocol_opened?(permission, protocol) && port_opened?(permission, port)
      end
    end

    def outbound_opened_only?(port = nil, protocol = nil, cidr = nil)
      permissions = resource_via_client.ip_permissions_egress.select do |permission|
        protocol_opened?(permission, protocol) && port_opened?(permission, port)
      end
      cidrs = []
      permissions.each do |permission|
        permission.ip_ranges.select { |ip_range| cidrs.push(ip_range.cidr_ip) }
      end
      cidrs == Array(cidr)
    end

    def inbound
      @inbound = true
      self
    end

    def outbound
      @inbound = false
      self
    end

    def ip_permissions_count
      resource_via_client.ip_permissions.count
    end
    alias inbound_permissions_count ip_permissions_count

    def ip_permissions_egress_count
      resource_via_client.ip_permissions_egress.count
    end
    alias outbound_permissions_count ip_permissions_egress_count

    def has_inbound_rule?(rule)
      resource_via_client.ip_permissions.find do |permission|
        sg_rule_match?(permission, rule)
      end
    end

    def inbound_rule_count
      resource_via_client.ip_permissions.reduce(0) do |sum, permission|
        sum += permission.ip_ranges.count + permission.user_id_group_pairs.count
      end
    end

    def has_outbound_rule?(rule)
      resource_via_client.ip_permissions_egress.find do |permission|
        sg_rule_match?(permission, rule)
      end
    end

    def outbound_rule_count
      resource_via_client.ip_permissions_egress.reduce(0) do |sum, permission|
        sum += permission.ip_ranges.count + permission.user_id_group_pairs.count
      end
    end

    private

    def cidr_opened?(permission, cidr)
      return true unless cidr

      ret = permission.prefix_list_ids.select do |prefix_list_id|
        prefix_list_id.prefix_list_id == cidr
      end
      return true if ret.count > 0

      ret = permission.ip_ranges.select do |ip_range|
        # if the cidr is an IP address then do a true CIDR match
        if cidr =~ /^\d+\.\d+\.\d+\.\d+/
          net = IPAddress::IPv4.new(ip_range.cidr_ip)
          net.include?(IPAddress::IPv4.new(cidr))
        else
          ip_range.cidr_ip == cidr
        end
      end
      return true if ret.count > 0

      ret = permission.user_id_group_pairs.select do |sg|
        # Compare the sg group_name if the remote group is in another account.
        # find_security_group call doesn't return info on a remote security group.
        if !sg.user_id.nil? && (sg.user_id != resource_via_client.owner_id)
          next (sg.group_name == cidr) || (sg.group_id == cidr)
        end
        next true if sg.group_id == cidr

        sg2 = find_security_group(sg.group_id)
        next false if sg2.nil?
        next true if sg2.group_name == cidr

        sg2.tags.find do |tag|
          tag.key == 'Name' && tag.value == cidr
        end
      end
      ret.count > 0
    end

    def protocol_opened?(permission, protocol)
      return true unless protocol
      return false if protocol == 'all' && permission.ip_protocol != '-1'
      return true if permission.ip_protocol == '-1'

      permission.ip_protocol == protocol
    end

    def port_opened?(permission, port)
      return true unless port
      return true unless permission.from_port
      return true unless permission.to_port

      port_between?(port, permission.from_port, permission.to_port)
    end

    def port_between?(port, from_port, to_port)
      if port.is_a?(String) && port.include?('-')
        f, t = port.split('-')
        from_port == f.to_i && to_port == t.to_i
      else
        port.between?(from_port, to_port)
      end
    end

    def sg_rule_match?(permission, rule)
      rule[:ip_protocol] = '-1' if rule[:ip_protocol] == 'all'
      return false unless permission.ip_protocol == rule[:ip_protocol]
      return false unless permission.ip_protocol == '-1' || permission.from_port == rule[:from_port]
      return false unless permission.ip_protocol == '-1' || permission.to_port == rule[:to_port]

      if rule[:ip_range]
        return false unless permission.ip_ranges.find do |ip_range|
          ip_range.cidr_ip == rule[:ip_range]
        end
      elsif rule[:group_pair]
        return false unless permission.user_id_group_pairs.find do |pair|
          group_pair_match?(pair, rule[:group_pair])
        end
      end
      true
    end

    def group_pair_match?(actual_pair, rule_pair)
      return false unless actual_pair.group_id == rule_pair[:group_id] || rule_pair[:group_id].nil?
      return false unless actual_pair.group_name == rule_pair[:group_name] || rule_pair[:group_name].nil?
      return false unless actual_pair.user_id == rule_pair[:user_id] || rule_pair[:user_id].nil?
      return false unless actual_pair.vpc_id == rule_pair[:vpc_id] || rule_pair[:vpc_id].nil?
      return false unless
        actual_pair.vpc_peering_connection_id == rule_pair[:vpc_peering_connection_id] ||
        rule_pair[:vpc_peering_connection_id].nil?
      return false unless actual_pair.peering_status == rule_pair[:peering_status] || rule_pair[:peering_status].nil?

      true
    end
  end
  # rubocop:enable Metrics/ClassLength
end
