<?php

namespace SlimStat\Services\Geolocation\Provider;

use SlimStat\Dependencies\GeoIp2\Database\Reader;
use SlimStat\Services\Geolocation\AbstractGeoIPProvider;

class MaxmindGeoIPProvider extends AbstractGeoIPProvider
{
	protected function init()
	{
		$this->dbType = 'maxmind';
		$precision    = $this->getPrecision();
		$this->dbName = 'city' === $precision ? 'GeoLite2-City.mmdb' : 'GeoLite2-Country.mmdb';
		$dir          = $this->getDbDir();
		$this->ensureDirExists($dir);
		$this->dbPath = $dir . '/' . $this->dbName;
		$license      = $this->getLicense();
		$edition      = 'city' === $precision ? 'GeoLite2-City' : 'GeoLite2-Country';

		// Validate license key format
		if (!$this->isValidLicenseKey($license)) {
			\wp_slimstat::update_option('slimstat_geoip_error', [
				'time'  => time(),
				'error' => __('Invalid MaxMind license key format. License key should be 16-40 characters containing only letters, numbers, and underscores.', 'wp-slimstat'),
			]);
		}

		// Direct download endpoint requires a license key
		$this->dbUrl = sprintf('https://download.maxmind.com/app/geoip_download?edition_id=%s&license_key=%s&suffix=tar.gz', $edition, rawurlencode($license));
	}

	public function locate($ip)
	{
		if (!file_exists($this->dbPath)) {
			return null;
		}

		try {
			$reader = new Reader($this->dbPath);
			$ip = sanitize_text_field($ip);

			// Determine which method to use based on database type
			$precision = $this->getPrecision();
			if ('city' === $precision) {
				$record = $reader->city($ip);
			} else {
				$record = $reader->country($ip);
			}

		$result = [
			'country_code' => $record->country->isoCode ?? $record->registeredCountry->isoCode ?? null,
			'continent'    => $record->continent->code ?? null,
			'provider'     => 'maxmind',
		];

		// Only include city-level data when precision is set to 'city'
		if ('city' === $precision) {
			$result['city']         = $record->city->name ?? null;
			$result['subdivision']  = $record->mostSpecificSubdivision->isoCode ?? null;
			$result['latitude']     = $record->location->latitude ?? null;
			$result['longitude']    = $record->location->longitude ?? null;
			$result['postal_code']  = $record->postal->code ?? null;
		}

		return $result;
		} catch (\Exception $e) {
			return null;
		}
	}

	public function updateDatabase()
	{
		try {
			// Validate license key before attempting download
			$license = $this->getLicense();
			if (!$this->isValidLicenseKey($license)) {
				\wp_slimstat::update_option('slimstat_geoip_error', [
					'time'  => time(),
					'error' => __('Invalid MaxMind license key format. License key should be 16-40 characters containing only letters, numbers, and underscores.', 'wp-slimstat'),
				]);
				return false;
			}

			// Check network connectivity
			if (!$this->checkConnectivity()) {
				return false;
			}

		// Download and extract the MaxMind database from tar.gz using WP APIs
		$this->ensureWpFileApiLoaded();
		WP_Filesystem();
		global $wp_filesystem;

		$tmp = wp_tempnam('mmdb');
		if (!$tmp) {
			\wp_slimstat::update_option('slimstat_geoip_error', [
				'time'  => time(),
				'error' => __('Failed to create temporary file for MaxMind database download.', 'wp-slimstat'),
			]);
			return false;
		}

		// Initialize cleanup variables before any operations to ensure they're always cleaned up
		$tgzPath = $tmp . '.tgz';
		$extractDir = null;
		$tarPath = null;
		$cleanup = function() use (&$extractDir, &$tarPath, &$tgzPath, $wp_filesystem) {
			if ($extractDir && is_dir($extractDir)) {
				$wp_filesystem->delete($extractDir, true);
			}
			if ($tarPath && file_exists($tarPath)) {
				$wp_filesystem->delete($tarPath);
			}
			if (file_exists($tgzPath)) {
				$wp_filesystem->delete($tgzPath);
			}
		};

		try {
			// Attempt 1: Stream download via WP HTTP API (mitigates cURL 56 by avoiding large in-memory buffers)
			$http_args = [
				'timeout'      => 90,
				'redirection'  => 5,
				'httpversion'  => '1.0',
				'headers'      => [
					'User-Agent' => 'WP-Slimstat MaxMind Updater',
					'Connection' => 'close',
				],
				'sslverify'    => true,
				'stream'       => true,
				'filename'     => $tgzPath,
			];
			$response = wp_remote_get($this->dbUrl, $http_args);
			if (is_wp_error($response) || (int) wp_remote_retrieve_response_code($response) !== 200) {
				// Attempt 2: Fallback to download_url helper
				$downloaded_file = download_url($this->dbUrl, 300);
				if (is_wp_error($downloaded_file)) {
					\wp_slimstat::update_option('slimstat_geoip_error', [
						'time'  => time(),
						'error' => sprintf(__('Network error downloading MaxMind database: %s', 'wp-slimstat'), $downloaded_file->get_error_message()),
					]);
					$cleanup();
					return false;
				}

				// Stage the downloaded file to $tgzPath
				if (!$wp_filesystem->move($downloaded_file, $tgzPath, true)) {
					$contents = $wp_filesystem->get_contents($downloaded_file);
					if ($contents === false || !$wp_filesystem->put_contents($tgzPath, $contents, FS_CHMOD_FILE)) {
						\wp_slimstat::update_option('slimstat_geoip_error', [
							'time'  => time(),
							'error' => __('Failed to stage downloaded MaxMind archive.', 'wp-slimstat'),
						]);
						$wp_filesystem->delete($downloaded_file);
						$cleanup();
						return false;
					}
					$wp_filesystem->delete($downloaded_file);
				}
			}

			// Try to extract mmdb from tar.gz using PharData if available
			if (!class_exists('PharData')) {
				\wp_slimstat::update_option('slimstat_geoip_error', [
					'time'  => time(),
					'error' => __('MaxMind update requires the PHP Phar extension (PharData class not found). Please enable Phar extension or upload the .mmdb file manually to wp-content/uploads/wp-slimstat/.', 'wp-slimstat'),
				]);
				$cleanup();
				return false;
			}

			$tgz     = new \PharData($tgzPath);
			$tarPath = $tmp . '.tar';
			$tgz->decompress();
			$tar = new \PharData($tarPath);

			$baseDir = dirname($this->dbPath);
			$this->ensureDirExists($baseDir);
			$extractDir = $baseDir . '/.mmdb_extract_' . wp_generate_password(8, false, false);

			// Create extraction directory and ensure it's tracked for cleanup
			if (!wp_mkdir_p($extractDir)) {
				\wp_slimstat::update_option('slimstat_geoip_error', [
					'time'  => time(),
					'error' => __('Failed to create temporary extraction directory.', 'wp-slimstat'),
				]);
				$cleanup();
				return false;
			}

			$tar->extractTo($extractDir, null, true);

			$mmdb_found = false;
			$files_in_archive = [];

			$iterator = new \RecursiveIteratorIterator(
				new \RecursiveDirectoryIterator($extractDir, \FilesystemIterator::SKIP_DOTS)
			);

			foreach ($iterator as $file) {
				if (!$file->isFile()) {
					continue;
				}
				$name = $file->getFilename();
				$files_in_archive[] = $name;
				if ('.mmdb' === substr($name, -5)) {
					$source = $file->getPathname();
					$this->ensureDirExists(dirname($this->dbPath));

					// Attempt to move the file first, then fall back to copy
					$move_success = $wp_filesystem->move($source, $this->dbPath, true);

					if (!$move_success) {
						$copy_success = $wp_filesystem->copy($source, $this->dbPath, true);

						if (!$copy_success) {
							$error_details = [];

							// Gather diagnostic information
							if (!$wp_filesystem->is_readable($source)) {
								$error_details[] = __('Source file is not readable', 'wp-slimstat');
							}
							if (!$wp_filesystem->is_writable(dirname($this->dbPath))) {
								$error_details[] = sprintf(__('Destination directory is not writable: %s', 'wp-slimstat'), dirname($this->dbPath));
							}
							if ($wp_filesystem->exists($this->dbPath) && !$wp_filesystem->is_writable($this->dbPath)) {
								$error_details[] = __('Destination file exists but is not writable', 'wp-slimstat');
							}

							$error_message = sprintf(
								__('.mmdb file was found but could not be moved or copied to destination. Source: %s, Destination: %s', 'wp-slimstat'),
								$source,
								$this->dbPath
							);

							if (!empty($error_details)) {
								$error_message .= ' ' . sprintf(__('Diagnostic info: %s', 'wp-slimstat'), implode('; ', $error_details));
							}

							\wp_slimstat::update_option('slimstat_geoip_error', [
								'time'  => time(),
								'error' => $error_message,
							]);
							$cleanup();
							return false;
						}
					}

					$mmdb_found = true;
					break;
				}
			}

			if (!$mmdb_found) {
				$file_list = implode(', ', array_unique($files_in_archive));
				\wp_slimstat::update_option('slimstat_geoip_error', [
					'time'  => time(),
					'error' => sprintf(__('No .mmdb file found in MaxMind database archive. Files found: %s', 'wp-slimstat'), $file_list),
				]);
				$cleanup();
				return false;
			}

			$final_exists = file_exists($this->dbPath);

			if ($final_exists) {
				$file_size = filesize($this->dbPath);
				\wp_slimstat::update_option('slimstat_geoip_error', []);
			}

			// Always cleanup temporary files, even on success
			$cleanup();
			return $final_exists;
		} catch (\Exception $exception) {
			\wp_slimstat::update_option('slimstat_geoip_error', [
				'time'  => time(),
				'error' => sprintf(__('Error extracting MaxMind database: %s', 'wp-slimstat'), $exception->getMessage()),
			]);
			// Ensure cleanup happens even when exception is thrown
			$cleanup();
			return false;
		}
		} catch (\Exception $e) {
			// Catch any fatal errors in the entire updateDatabase method
			\wp_slimstat::update_option('slimstat_geoip_error', [
				'time'  => time(),
				'error' => sprintf(__('Fatal error updating MaxMind database: %s', 'wp-slimstat'), $e->getMessage()),
			]);
			return false;
		}
	}

	protected function isValidLicenseKey($license)
	{
		if (empty($license)) {
			return false;
		}
		return (bool) preg_match('/^[A-Za-z0-9_]{16,40}$/', (string) $license);
	}

	protected function checkConnectivity()
	{
		try {
			$host = 'download.maxmind.com';
			$ip = gethostbyname($host);
			if ($ip === $host) {
				\wp_slimstat::update_option('slimstat_geoip_error', [
					'time'  => time(),
					'error' => sprintf(__('DNS resolution failed for %s. Please check your internet connection and DNS settings.', 'wp-slimstat'), $host),
				]);
				return false;
			}
			$test_response = wp_remote_get('https://download.maxmind.com/', ['timeout' => 30]);
			if (is_wp_error($test_response)) {
				\wp_slimstat::update_option('slimstat_geoip_error', [
					'time'  => time(),
					'error' => sprintf(__('Cannot connect to MaxMind servers. Network error: %s', 'wp-slimstat'), $test_response->get_error_message()),
				]);
				return false;
			}
			return true;
		} catch (\Exception $e) {
			\wp_slimstat::update_option('slimstat_geoip_error', [
				'time'  => time(),
				'error' => sprintf(__('Network connectivity check failed: %s', 'wp-slimstat'), $e->getMessage()),
			]);
			return false;
		}
	}

	protected function getDetailedHttpError($response_code, $response_body)
	{
		$base_msg = sprintf(__('HTTP %d error downloading MaxMind database', 'wp-slimstat'), $response_code);
		switch ($response_code) {
			case 401:
				return $base_msg . ': ' . __('Unauthorized. Please check your MaxMind license key. The key may be invalid, expired, or not authorized for GeoLite2 downloads.', 'wp-slimstat');
			case 403:
				return $base_msg . ': ' . __('Forbidden. Your license key does not have permission to download this database, or you have exceeded the download limit.', 'wp-slimstat');
			case 404:
				return $base_msg . ': ' . __('Database not found. The requested edition may not exist or may not be available for your account type.', 'wp-slimstat');
			case 429:
				return $base_msg . ': ' . __('Too many requests. You have exceeded the download limit. Please wait before trying again.', 'wp-slimstat');
			case 500:
			case 502:
			case 503:
			case 504:
				return $base_msg . ': ' . __('MaxMind server error. Please try again later.', 'wp-slimstat');
			default:
				$error_msg = $base_msg;
				if (!empty($response_body)) {
					$body_preview = substr($response_body, 0, 200);
					if (strpos($body_preview, 'license key') !== false || strpos($body_preview, 'authentication') !== false) {
						$error_msg .= ': ' . __('License key authentication failed. Please verify your MaxMind license key.', 'wp-slimstat');
					} else {
						$error_msg .= ': ' . $body_preview;
					}
				}
				return $error_msg;
		}
	}
}
