<?php
/**
 * @package Hafenstudios\Wellenbrecher
 */

namespace Hafenstudios\Wellenbrecher\Pro\GeoIp;

use MaxMind\Db\Reader;

defined( 'ABSPATH' ) || exit;

/**
 * Duenne Huelle um die DB-IP-Lite-MMDBs (gelesen mit maxmind-db/reader,
 * Apache 2.0, reiner PHP-Pfad). Kapselt Datei-I/O und Reader-Ausnahmen, damit
 * die Regeln (CountryRule, AsnDatacenterRule) reine Bewerter bleiben.
 *
 * Fail-open an jeder Ecke (Bauplan Kap. 4, Produktversprechen Nr. 6): fehlt die
 * Datei, ist sie veraltet/kaputt oder wirft der Reader, liefern country()/asn()
 * einfach '' bzw. 0 - die Regel feuert dann nicht, es entsteht nie ein Fehler.
 *
 * Nicht final und mit ueberschreibbaren Methoden (Haus-Muster K4/W5a): Tests
 * leiten ab und spiegeln feste Antworten, ohne echte MMDB-Dateien zu brauchen.
 *
 * Attribution: "IP Geolocation by DB-IP" (https://db-ip.com), CC BY 4.0 -
 * ausgewiesen im Settings-Block (Admin\ExternaPage) und in den readme-Credits.
 */
class GeoDatabase {

	protected string $country_path;

	protected string $asn_path;

	/**
	 * @var Reader|null Lazy geoeffneter Country-Reader.
	 */
	private ?Reader $country_reader = null;

	/**
	 * @var Reader|null Lazy geoeffneter ASN-Reader.
	 */
	private ?Reader $asn_reader = null;

	/**
	 * @param string $country_path Pfad zur dbip-country-lite.mmdb (leer = keine).
	 * @param string $asn_path     Pfad zur dbip-asn-lite.mmdb (leer = keine).
	 */
	public function __construct( string $country_path = '', string $asn_path = '' ) {
		$this->country_path = $country_path;
		$this->asn_path     = $asn_path;
	}

	/**
	 * Ist die Country-Datenbank vorhanden und lesbar?
	 *
	 * @return bool
	 */
	public function has_country(): bool {
		return '' !== $this->country_path && is_readable( $this->country_path );
	}

	/**
	 * Ist die ASN-Datenbank vorhanden und lesbar?
	 *
	 * @return bool
	 */
	public function has_asn(): bool {
		return '' !== $this->asn_path && is_readable( $this->asn_path );
	}

	/**
	 * ISO-3166-Alpha-2-Laendercode zur IP (Grossbuchstaben) oder '' bei jedem
	 * Fehlschlag.
	 *
	 * @param string $ip Klartext-IP.
	 * @return string
	 */
	public function country( string $ip ): string {
		$record = $this->lookup( 'country', $ip );
		$code   = $record['country']['iso_code'] ?? '';
		return is_string( $code ) ? strtoupper( $code ) : '';
	}

	/**
	 * Autonome System-Nummer zur IP oder 0 bei jedem Fehlschlag.
	 *
	 * @param string $ip Klartext-IP.
	 * @return int
	 */
	public function asn( string $ip ): int {
		$record = $this->lookup( 'asn', $ip );
		$number = $record['autonomous_system_number'] ?? 0;
		return is_numeric( $number ) ? (int) $number : 0;
	}

	/**
	 * Nachschlagen in einer der beiden Datenbanken. Kapselt Oeffnen und
	 * Reader-Ausnahmen; liefert das Roh-Record-Array oder array() bei Fehler.
	 *
	 * @param string $which country|asn.
	 * @param string $ip    Klartext-IP.
	 * @return array<string, mixed>
	 */
	protected function lookup( string $which, string $ip ): array {
		$ip = trim( $ip );
		if ( '' === $ip ) {
			return array();
		}

		try {
			$reader = $this->reader( $which );
			if ( ! $reader instanceof Reader ) {
				return array();
			}
			$record = $reader->get( $ip );
			return is_array( $record ) ? $record : array();
		} catch ( \Throwable $e ) {
			// Ungueltige IP, kaputte DB, fehlende Extension - fail-open.
			return array();
		}
	}

	/**
	 * Oeffnet (lazy, gecacht) den passenden Reader oder null, wenn die Datei fehlt.
	 *
	 * @param string $which country|asn.
	 * @return Reader|null
	 */
	private function reader( string $which ): ?Reader {
		if ( 'country' === $which ) {
			if ( null === $this->country_reader && $this->has_country() ) {
				$this->country_reader = new Reader( $this->country_path );
			}
			return $this->country_reader;
		}
		if ( null === $this->asn_reader && $this->has_asn() ) {
			$this->asn_reader = new Reader( $this->asn_path );
		}
		return $this->asn_reader;
	}
}
