<?php
namespace Misuzu\GeoIP;

use InvalidArgumentException;
use RuntimeException;
use Index\Net\IPAddress;
use GeoIp2\Database\Reader;
use GeoIp2\Exception\AddressNotFoundException;
use GeoIp2\Model\Asn;
use GeoIp2\Model\City;
use GeoIp2\Model\Country;
use Misuzu\Config\IConfig;

class GeoIPHelper {
    public const ASN = 'asn';
    public const CITY = 'city';
    public const COUNTRY = 'country';

    private IConfig $config;

    private array $readers = [];

    public function __construct(IConfig $config) {
        $this->config = $config;
    }

    public function hasDbPath(string $type): bool {
        return array_key_exists($type, $this->readers)
            || $this->config->hasValue('db.' . $type);
    }

    public function getDbPath(string $type): string {
        $type = 'db.' . $type;
        if(!$this->config->hasValue($type))
            throw new RuntimeException('Not database path has been configured for $type.');
        return $this->config->getValue($type, IConfig::T_STR);
    }

    public function getReader(string $type): Reader {
        if(!array_key_exists($type, $this->readers))
            return $this->readers[$type] = new Reader($this->getDbPath($type));
        return $this->readers[$type];
    }

    public function getASN(IPAddress|string $ipAddress): Asn {
        if($ipAddress instanceof IPAddress)
            $ipAddress = $ipAddress->getCleanAddress();

        if($this->hasDbPath(self::ASN))
            return $this->getReader(self::ASN)->asn($ipAddress);

        throw new RuntimeException('There was no database available that could satisfy this request.');
    }

    public function getCityOrCountry(IPAddress|string $ipAddress): City|Country {
        if($ipAddress instanceof IPAddress)
            $ipAddress = $ipAddress->getCleanAddress();

        if($this->hasDbPath(self::CITY))
            return $this->getReader(self::CITY)->city($ipAddress);
        if($this->hasDbPath(self::COUNTRY))
            return $this->getReader(self::COUNTRY)->country($ipAddress);

        throw new RuntimeException('There was no database available that could satisfy this request.');
    }

    public function getIsoCountryCode(IPAddress|string $ipAddress, string $fallback = 'XX'): string {
        try {
            return $this->getCityOrCountry($ipAddress)->country->isoCode ?? $fallback;
        } catch(AddressNotFoundException $ex) {
            return $fallback;
        } catch(RuntimeException $ex) {
            return $fallback;
        }
    }
}
