# frozen_string_literal: true

require 'maxmind/db'
require 'httparty'
require 'digest'
require 'tmpdir'
require 'fileutils'

class GeoIpService
  include HTTParty

  class DatabaseNotAvailable < StandardError; end
  class InvalidIpAddress < StandardError; end

  attr_reader :database_reader, :database_path

  def initialize(database_path: nil)
    @database_path = database_path || default_database_path
    @database_reader = nil
    load_database if File.exist?(@database_path)
  end

  # Main lookup method - returns country code for IP address
  def lookup_country(ip_address)
    return fallback_country unless database_available?
    return fallback_country unless valid_ip?(ip_address)

    result = database_reader.get(ip_address)
    return fallback_country if result.nil? || result.empty?

    # Extract country code from MaxMind result
    result['country']&.[]('iso_code') || fallback_country
  rescue => e
    Rails.logger.error "GeoIP lookup failed for #{ip_address}: #{e.message}"
    fallback_country
  end

  # Check if database is available and loaded
  def database_available?
    return false unless File.exist?(@database_path)

    load_database unless database_reader
    database_reader.present?
  end

  # Get database information
  def database_info
    return nil unless File.exist?(@database_path)

    file_stat = File.stat(@database_path)
    metadata = database_reader&.metadata

    if metadata
      {
        type: 'GeoLite2-Country',
        version: "#{metadata.binary_format_major_version}.#{metadata.binary_format_minor_version}",
        size: file_stat.size,
        modified_at: file_stat.mtime,
        age_days: ((Time.current - file_stat.mtime) / 1.day).round,
        file_path: @database_path
      }
    else
      {
        type: 'GeoLite2-Country',
        version: 'Unknown',
        size: file_stat.size,
        modified_at: file_stat.mtime,
        age_days: ((Time.current - file_stat.mtime) / 1.day).round,
        file_path: @database_path
      }
    end
  end

  # Class method for convenience lookup
  def self.lookup_country(ip_address)
    new.lookup_country(ip_address)
  end

  # Update database from remote source
  def self.update_database!
    new.update_from_remote!
  end

  # Download and install database from remote URL
  def update_from_remote!
    config = Rails.application.config.maxmind
    database_url = config.database_url
    storage_path = config.storage_path
    database_filename = config.database_filename
    temp_file = nil

    Rails.logger.info "Starting GeoIP database download from #{database_url}"

    begin
      # Ensure storage directory exists
      FileUtils.mkdir_p(storage_path) unless Dir.exist?(storage_path)

      # Download to temporary file
      Dir.mktmpdir do |temp_dir|
        temp_file = File.join(temp_dir, database_filename)

        response = HTTParty.get(database_url, timeout: 60)
        raise "Failed to download database: #{response.code}" unless response.success?

        File.binwrite(temp_file, response.body)

        # Validate downloaded file
        validate_downloaded_file(temp_file)

        # Move to final location
        final_path = File.join(storage_path, database_filename)
        File.rename(temp_file, final_path)

        # Reload the database with new file
        @database_reader = nil
        load_database

        Rails.logger.info "GeoIP database successfully updated: #{final_path}"
        return true
      end
    rescue => e
      Rails.logger.error "Failed to update GeoIP database: #{e.message}"
      File.delete(temp_file) if temp_file && File.exist?(temp_file)
      false
    end
  end

  private

  def load_database
    return unless File.exist?(@database_path)

    @database_reader = MaxMind::DB.new(@database_path)
  rescue => e
    Rails.logger.error "Failed to load GeoIP database: #{e.message}"
    @database_reader = nil
  end

  def default_database_path
    config = Rails.application.config.maxmind
    File.join(config.storage_path, config.database_filename)
  end

  def valid_ip?(ip_address)
    IPAddr.new(ip_address)
    true
  rescue IPAddr::InvalidAddressError
    false
  end

  def fallback_country
    config = Rails.application.config.maxmind
    return nil unless config.enable_fallback
    config.fallback_country
  end

  def cache_size
    return 0 unless Rails.application.config.maxmind.cache_enabled
    Rails.application.config.maxmind.cache_size
  end

  def validate_downloaded_file(file_path)
    # Basic file existence and size check
    raise "Downloaded file is empty" unless File.exist?(file_path)
    raise "Downloaded file is too small" if File.size(file_path) < 1_000_000 # ~1MB minimum

    # Try to open with MaxMind reader
    begin
      MaxMind::DB.new(file_path)
    rescue => e
      raise "Invalid MaxMind database format: #{e.message}"
    end
  end
end
