<?php

namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers;

use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecord;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\DNSRecordType;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Entities\Hostname;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Mappers\MapperInterface;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events\DNSQueried;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events\DNSQueryFailed;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Events\DNSQueryProfiled;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Traits\Dispatcher;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Traits\Logger;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Traits\Profileable;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces\ObservableResolver;
use function array_map;
use function array_pop;
use function explode;
use function get_class;
use function json_encode;
abstract class ResolverAbstract implements ObservableResolver
{
    use Logger;
    use Dispatcher;
    use Profileable;
    private string $name;
    /**
     * @var string
     */
    private const EVENT = 'event';
    public function getName() : string
    {
        if (!isset($this->name)) {
            $explodedClass = explode('\\', get_class($this));
            $this->name = array_pop($explodedClass);
        }
        return $this->name;
    }
    public function getARecords(string $hostname) : DNSRecordCollection
    {
        return $this->getRecords($hostname, (string) DNSRecordType::createA());
    }
    public function getAAAARecords(string $hostname) : DNSRecordCollection
    {
        return $this->getRecords($hostname, (string) DNSRecordType::createAAAA());
    }
    public function getCNAMERecords(string $hostname) : DNSRecordCollection
    {
        return $this->getRecords($hostname, (string) DNSRecordType::createCNAME());
    }
    public function getTXTRecords(string $hostname) : DNSRecordCollection
    {
        return $this->getRecords($hostname, (string) DNSRecordType::createTXT());
    }
    public function getMXRecords(string $hostname) : DNSRecordCollection
    {
        return $this->getRecords($hostname, (string) DNSRecordType::createMX());
    }
    public function recordTypeExists(string $hostname, string $recordType) : bool
    {
        return !$this->getRecords($hostname, $recordType)->isEmpty();
    }
    public function hasRecord(DNSRecordInterface $record) : bool
    {
        return $this->getRecords((string) $record->getHostname(), (string) $record->getType())->has($record);
    }
    /**
     * @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
     */
    public function getRecords(string $hostname, string $recordType = null) : DNSRecordCollection
    {
        $recordType = DNSRecordType::createFromString($recordType ?? 'ANY');
        $hostname = Hostname::createFromString($hostname);
        $profile = $this->createProfile("{$this->getName()}:{$hostname}:{$recordType}");
        $profile->startTransaction();
        try {
            $result = $recordType->equals(DNSRecordType::createANY()) ? $this->doQuery($hostname, $recordType) : $this->doQuery($hostname, $recordType)->filteredByType($recordType);
        } catch (QueryFailure $e) {
            $dnsQueryFailureEvent = new DNSQueryFailed($this, $hostname, $recordType, $e);
            $this->dispatch($dnsQueryFailureEvent);
            $this->getLogger()->error('DNS query failed', [self::EVENT => json_encode($dnsQueryFailureEvent), 'exception' => $e]);
            throw $e;
        } finally {
            $profile->endTransaction();
            $profile->samplePeakMemoryUsage();
            $dnsQueryProfiledEvent = new DNSQueryProfiled($profile);
            $this->dispatch($dnsQueryProfiledEvent);
            $this->getLogger()->info('DNS query profiled', [self::EVENT => json_encode($dnsQueryProfiledEvent)]);
        }
        $dnsQueriedEvent = new DNSQueried($this, $hostname, $recordType, $result);
        $this->dispatch($dnsQueriedEvent);
        $this->getLogger()->info('DNS queried', [self::EVENT => json_encode($dnsQueriedEvent)]);
        return $result;
    }
    public function mapResults(MapperInterface $mapper, array $results) : DNSRecordCollection
    {
        $collection = new DNSRecordCollection();
        array_map(function (array $fields) use(&$collection, $mapper) {
            try {
                $collection[] = $mapper->mapFields($fields)->toDNSRecord();
            } catch (InvalidArgumentException $e) {
                $this->getLogger()->warning('Invalid fields passed to mapper', $fields);
            }
        }, $results);
        return $collection;
    }
    /**
     * @throws \RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure
     */
    protected abstract function doQuery(Hostname $hostname, DNSRecordType $recordType) : DNSRecordCollection;
}
