# frozen_string_literal: true

# Ipv6Range - Stores IPv6 network ranges with IP intelligence metadata
#
# Optimized for fast range lookups using network_start/network_end binary storage.
# Stores metadata about IP ranges including ASN, company, geographic info,
# and flags for datacenter/proxy/VPN detection.
class Ipv6Range < ApplicationRecord
  # Validations
  validates :network_start, presence: true
  validates :network_end, presence: true
  validates :network_prefix, presence: true,
            numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 128 }

  # Callbacks
  before_validation :calculate_range, if: -> { cidr.present? }

  # Scopes for common queries
  scope :datacenter, -> { where(is_datacenter: true) }
  scope :proxy, -> { where(is_proxy: true) }
  scope :vpn, -> { where(is_vpn: true) }
  scope :by_country, ->(country) { where(ip_api_country: country) }
  scope :by_company, ->(company) { where(company: company) }
  scope :by_asn, ->(asn) { where(asn: asn) }

  # Virtual attribute for setting IP via CIDR notation
  attr_accessor :cidr

  # Find ranges that contain a specific IPv6 address
  def self.contains_ip(ip_string)
    ip_addr = IPAddr.new(ip_string)
    raise ArgumentError, "Not an IPv6 address" unless ip_addr.ipv6?

    ip_bytes = ip_addr.hton

    where("? BETWEEN network_start AND network_end", ip_bytes)
      .order(network_prefix: :desc) # Most specific first
  end

  # Check if this range contains a specific IP
  def contains_ip?(ip_string)
    ip_addr = IPAddr.new(ip_string)
    return false unless ip_addr.ipv6?

    ip_bytes = ip_addr.hton
    ip_bytes >= network_start && ip_bytes <= network_end
  end

  # Get CIDR notation for this range
  def to_cidr
    return nil unless network_start.present?

    ip_addr = IPAddr.new_ntoh(network_start)
    "#{ip_addr}/#{network_prefix}"
  end

  # String representation
  def to_s
    to_cidr || "Ipv6Range##{id}"
  end

  # Convenience methods for JSON fields
  def abuser_scores_hash
    abuser_scores ? JSON.parse(abuser_scores) : {}
  rescue JSON::ParserError
    {}
  end

  def abuser_scores_hash=(hash)
    self.abuser_scores = hash.to_json
  end

  def additional_data_hash
    additional_data ? JSON.parse(additional_data) : {}
  rescue JSON::ParserError
    {}
  end

  def additional_data_hash=(hash)
    self.additional_data = hash.to_json
  end

  # GeoIP lookup methods
  def geo_lookup_country!
    return if ip_api_country.present? || geo2_country.present?

    # Use the first IP in the range for lookup
    sample_ip = IPAddr.new_ntoh(network_start).to_s
    country = GeoIpService.lookup_country(sample_ip)

    if country.present?
      # Update both country fields for redundancy
      update!(ip_api_country: country, geo2_country: country)
      country
    end
  rescue => e
    Rails.logger.error "Failed to lookup geo location for IPv6 range #{id}: #{e.message}"
    nil
  end

  def geo_lookup_country
    return ip_api_country if ip_api_country.present?
    return geo2_country if geo2_country.present?

    # Use the first IP in the range for lookup
    sample_ip = IPAddr.new_ntoh(network_start).to_s
    GeoIpService.lookup_country(sample_ip)
  rescue => e
    Rails.logger.error "Failed to lookup geo location for IPv6 range #{id}: #{e.message}"
    nil
  end

  # Check if this range has any country information
  def has_country_info?
    ip_api_country.present? || geo2_country.present?
  end

  # Get the best available country code
  def primary_country
    ip_api_country || geo2_country
  end

  # Class method to lookup country for any IP in the range
  def self.lookup_country_by_ip(ip_string)
    range = contains_ip(ip_string).first
    return nil unless range

    range.geo_lookup_country
  end

  # Class method to enrich ranges without country data
  def self.enrich_missing_geo_data(limit: 1000)
    ranges_without_geo = where(ip_api_country: [nil, ''], geo2_country: [nil, ''])
                          .limit(limit)

    updated_count = 0
    geo_service = GeoIpService.new

    ranges_without_geo.find_each do |range|
      country = geo_service.lookup_country(IPAddr.new_ntoh(range.network_start).to_s)
      if country.present?
        range.update!(ip_api_country: country, geo2_country: country)
        updated_count += 1
      end
    end

    updated_count
  end

  private

  # Calculate network_start and network_end from CIDR notation
  def calculate_range
    return unless cidr.present?

    ip_addr = IPAddr.new(cidr)
    raise ArgumentError, "Not an IPv6 CIDR" unless ip_addr.ipv6?

    # Get prefix from CIDR
    self.network_prefix = cidr.split("/").last.to_i

    # Calculate network range (binary format for IPv6)
    first_ip = ip_addr.to_range.first
    last_ip = ip_addr.to_range.last

    self.network_start = first_ip.hton
    self.network_end = last_ip.hton
  rescue IPAddr::InvalidAddressError => e
    errors.add(:cidr, "invalid IPv6 CIDR notation: #{e.message}")
  end
end
