<?php

namespace WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers;

use WP_Ultimo\Dependencies\Psr\Log\LoggerAwareInterface;
use WP_Ultimo\Dependencies\Psr\Log\LoggerInterface;
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\Exception;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Observability\Interfaces\Observable;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces;
use WP_Ultimo\Dependencies\RemotelyLiving\PHPDNS\Resolvers\Interfaces\Resolver;
use WP_Ultimo\Dependencies\Symfony\Component\EventDispatcher\EventSubscriberInterface;
use function json_encode;
use function shuffle;
final class Chain extends ResolverAbstract implements Interfaces\Chain
{
    public const STRATEGY_FIRST_TO_FIND = 0;
    public const STRATEGY_ALL_RESULTS = 1;
    public const STRATEGY_CONSENSUS = 2;
    /**
     * @var \RemotelyLiving\PHPDNS\Resolvers\Interfaces\Resolver[]
     */
    private array $resolvers = [];
    private int $callThroughStrategy = self::STRATEGY_FIRST_TO_FIND;
    public function __construct(Resolver ...$resolvers)
    {
        foreach ($resolvers as $resolver) {
            $this->pushResolver($resolver);
        }
    }
    public function pushResolver(Resolver ...$resolvers) : void
    {
        foreach ($resolvers as $resolver) {
            $this->resolvers[] = $resolver;
        }
    }
    public function withAllResults() : Interfaces\Chain
    {
        $all = new self(...$this->resolvers);
        $all->callThroughStrategy = self::STRATEGY_ALL_RESULTS;
        return $all;
    }
    public function withFirstResults() : Interfaces\Chain
    {
        $first = new self(...$this->resolvers);
        $first->callThroughStrategy = self::STRATEGY_FIRST_TO_FIND;
        return $first;
    }
    public function withConsensusResults() : Interfaces\Chain
    {
        $consensus = new self(...$this->resolvers);
        $consensus->callThroughStrategy = self::STRATEGY_CONSENSUS;
        return $consensus;
    }
    public function randomly() : Interfaces\Chain
    {
        $randomized = clone $this;
        shuffle($randomized->resolvers);
        return $randomized;
    }
    public function addSubscriber(EventSubscriberInterface $subscriber) : void
    {
        foreach ($this->resolvers as $resolver) {
            if ($resolver instanceof Observable) {
                $resolver->addSubscriber($subscriber);
            }
        }
    }
    public function addListener(string $eventName, callable $listener, int $priority = 0) : void
    {
        foreach ($this->resolvers as $resolver) {
            if ($resolver instanceof Observable) {
                $resolver->addListener($eventName, $listener, $priority);
            }
        }
    }
    public function setLogger(LoggerInterface $logger) : void
    {
        foreach ($this->resolvers as $resolver) {
            if ($resolver instanceof LoggerAwareInterface) {
                $resolver->setLogger($logger);
            }
        }
    }
    public function hasRecord(DNSRecordInterface $record) : bool
    {
        foreach ($this->resolvers as $resolver) {
            if ($resolver->hasRecord($record)) {
                return \true;
            }
        }
        return \false;
    }
    protected function doQuery(Hostname $hostname, DNSRecordType $recordType) : DNSRecordCollection
    {
        $merged = [];
        foreach ($this->resolvers as $resolver) {
            try {
                $records = $resolver->getRecords((string) $hostname, (string) $recordType);
            } catch (Exception $e) {
                $this->getLogger()->error('Something went wrong in the chain of resolvers', ['exception' => json_encode($e), 'resolver' => $resolver->getName()]);
                continue;
            }
            if ($this->callThroughStrategy === self::STRATEGY_FIRST_TO_FIND && !$records->isEmpty()) {
                return $records;
            }
            /** @var DNSRecord $record */
            foreach ($records as $record) {
                $merged[] = $record;
            }
        }
        $collection = new DNSRecordCollection(...$merged);
        return $this->callThroughStrategy === self::STRATEGY_CONSENSUS ? $collection->withUniqueValuesExcluded() : $collection->withUniqueValues();
    }
}
