<?php
/**
 * MaxMind GeoIP2 City Database Lookup Class
 *
 * @package SPAI_Contact_Form
 */

class SPAI_GeoIP {
    
    /**
     * Database file path
     */
    private $db_path;
    
    /**
     * Timeout in seconds
     */
    private $timeout = 0.5;
    
    /**
     * Reader instance
     */
    private $reader = null;
    
    /**
     * Constructor
     */
    public function __construct() {
        $this->db_path = SPAI_CONTACT_FORM_PLUGIN_DIR . 'assets/geoip/GeoLite2-City.mmdb';
        $this->load_maxmind_libraries();
    }
    
    /**
     * Load MaxMind libraries
     */
    private function load_maxmind_libraries() {
        $vendor_path = SPAI_CONTACT_FORM_PLUGIN_DIR . 'assets/vendor/';
        
        // Load MaxMind DB Reader
        require_once $vendor_path . 'maxmind-db/src/MaxMind/Db/Reader.php';
        require_once $vendor_path . 'maxmind-db/src/MaxMind/Db/Reader/Decoder.php';
        require_once $vendor_path . 'maxmind-db/src/MaxMind/Db/Reader/InvalidDatabaseException.php';
        require_once $vendor_path . 'maxmind-db/src/MaxMind/Db/Reader/Metadata.php';
        require_once $vendor_path . 'maxmind-db/src/MaxMind/Db/Reader/Util.php';
        
        // Load GeoIP2 exceptions
        require_once $vendor_path . 'geoip2/src/Exception/GeoIp2Exception.php';
        require_once $vendor_path . 'geoip2/src/Exception/AddressNotFoundException.php';
        
        // Load GeoIP2 interfaces
        require_once $vendor_path . 'geoip2/src/ProviderInterface.php';
        
        // Load record classes (base classes first)
        require_once $vendor_path . 'geoip2/src/Record/AbstractRecord.php';
        require_once $vendor_path . 'geoip2/src/Record/AbstractPlaceRecord.php';
        require_once $vendor_path . 'geoip2/src/Record/City.php';
        require_once $vendor_path . 'geoip2/src/Record/Continent.php';
        require_once $vendor_path . 'geoip2/src/Record/Country.php';
        require_once $vendor_path . 'geoip2/src/Record/Location.php';
        require_once $vendor_path . 'geoip2/src/Record/MaxMind.php';
        require_once $vendor_path . 'geoip2/src/Record/Postal.php';
        require_once $vendor_path . 'geoip2/src/Record/RepresentedCountry.php';
        require_once $vendor_path . 'geoip2/src/Record/Subdivision.php';
        require_once $vendor_path . 'geoip2/src/Record/Traits.php';
        
        // Load model classes (base class first)
        require_once $vendor_path . 'geoip2/src/Model/AbstractModel.php';
        require_once $vendor_path . 'geoip2/src/Model/Country.php';
        require_once $vendor_path . 'geoip2/src/Model/City.php';
        
        // Load utility classes
        require_once $vendor_path . 'geoip2/src/Util.php';
        
        // Load database reader
        require_once $vendor_path . 'geoip2/src/Database/Reader.php';
    }
    
    /**
     * Check if IP is private/local
     */
    private function is_private_ip($ip) {
        return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false;
    }
    
    /**
     * Initialize reader with timeout protection
     */
    private function get_reader() {
        if ($this->reader !== null) {
            return $this->reader;
        }
        
        $start_time = microtime(true);
        
        try {
            if (!file_exists($this->db_path)) {
                return null;
            }
            
            // Check timeout during file operations
            if ((microtime(true) - $start_time) > $this->timeout) {
                return null;
            }
            
            $this->reader = new \GeoIp2\Database\Reader($this->db_path);
            
            // Final timeout check
            if ((microtime(true) - $start_time) > $this->timeout) {
                $this->reader = null;
                return null;
            }
            
            return $this->reader;
            
        } catch (Exception $e) {
            return null;
        }
    }
    
    /**
     * Lookup IP geolocation data with city information
     */
    public function lookup($ip) {
        // Skip private/local IPs
        if ($this->is_private_ip($ip) || $ip === '0.0.0.0' || $ip === '127.0.0.1') {
            return null;
        }
        
        $start_time = microtime(true);
        
        try {
            $reader = $this->get_reader();
            if (!$reader) {
                return null;
            }
            
            // Check timeout before lookup
            if ((microtime(true) - $start_time) > $this->timeout) {
                return null;
            }
            
            $record = $reader->city($ip);
            
            // Check timeout after lookup
            if ((microtime(true) - $start_time) > $this->timeout) {
                return null;
            }
            
            // Extract city, subdivision (state/region), and country data
            $city = $record->city->name ?? null;
            $subdivision = $record->mostSpecificSubdivision->name ?? null;
            $country_code = $record->country->isoCode ?? null;
            $country_name = $record->country->name ?? null;
            
            if ($country_code) {
                return array(
                    'city' => $city,
                    'subdivision' => $subdivision,
                    'country_code' => $country_code,
                    'country' => $country_name
                );
            }
            
            return null;
            
        } catch (Exception $e) {
            // Return null on any error (including AddressNotFoundException)
            return null;
        }
    }
    
    /**
     * Format IP with geo data (City, State/Region, Country format)
     */
    public function format_ip_with_geo($ip) {
        $geo_data = $this->lookup($ip);
        
        if ($geo_data && isset($geo_data['country_code'])) {
            $city = $geo_data['city'] ?? null;
            $subdivision = $geo_data['subdivision'] ?? null;
            $country = $geo_data['country_code'];
            
            // Unified format: City, State/Region, Country
            if ($city && $subdivision) {
                // Format: IP: 185.13.187.174 (Chicago, IL, US) or (Rzeszów, Lesser Poland, PL)
                return $ip . ' (' . $city . ', ' . $subdivision . ', ' . $country . ')';
            } elseif ($city) {
                // Format: IP: 185.13.187.174 (City, Country) - when no subdivision data
                return $ip . ' (' . $city . ', ' . $country . ')';
            } else {
                // Format: IP: 185.13.187.174 (Country) - when no city data
                return $ip . ' (' . $country . ')';
            }
        }
        
        // Fallback to IP only
        return $ip;
    }
}
?>