<?php

namespace App\EventSubscriber;

use ApiPlatform\Symfony\EventListener\EventPriorities;
use App\Controller\OgBoot\PxeBootFile\PostAction;
use App\Dto\Output\OrganizationalUnitOutput;
use App\Entity\Client;
use App\Entity\NetworkSettings;
use App\Entity\OrganizationalUnit;
use App\Model\OrganizationalUnitTypes;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;

final readonly class OrganizationalUnitSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private EntityManagerInterface $entityManager,
        private readonly PostAction $postAction,
    )
    {

    }

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::VIEW => ['updateNetworkSettings', EventPriorities::POST_WRITE],
        ];
    }

    /**
     * @throws TransportExceptionInterface
     * @throws ServerExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ClientExceptionInterface
     */
    public function updateNetworkSettings(ViewEvent $event): void
    {
        $organizationalUnitOutput = $event->getControllerResult();
        $method = $event->getRequest()->getMethod();

        if (!$organizationalUnitOutput instanceof OrganizationalUnitOutput ||
            !in_array($method, [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH])) {
            return;
        }

        /** @var OrganizationalUnit $organizationalUnitEntity */
        $organizationalUnitEntity = $organizationalUnitOutput->getEntity();

        if ($organizationalUnitEntity->getNetworkSettings() === null || $organizationalUnitEntity->isExcludeParentChanges()) {
            return;
        }

        $newNetworkSettings = $this->cloneNetworkSettings($organizationalUnitEntity->getNetworkSettings());

        $this->updateChildrenNetworkSettings($organizationalUnitEntity, $newNetworkSettings);

        if ($organizationalUnitEntity->getType() === OrganizationalUnitTypes::CLASSROOM) {
            $this->syncOgBoot($organizationalUnitEntity);
        }
    }


    private function updateChildrenNetworkSettings(OrganizationalUnit $parentUnit, NetworkSettings $networkSettings): void
    {
        /** @var OrganizationalUnit $childUnit */
        foreach ($parentUnit->getOrganizationalUnits() as $childUnit) {
            if ($childUnit->isExcludeParentChanges()) {
                $childUnit->setNetworkSettings(null);
            } else{

                $childUnit->setNetworkSettings($networkSettings);

                foreach ($childUnit->getClients() as $client) {
                    $client->setOgLive($networkSettings->getOgLive());
                    $client->setMenu($networkSettings->getMenu());
                    $client->setRepository($networkSettings->getRepository());
                    $client->setTemplate($networkSettings->getPxeTemplate());
                    $this->entityManager->persist($client);
                }
            }
            $this->entityManager->persist($childUnit);
            $this->entityManager->flush();

            $this->updateChildrenNetworkSettings($childUnit, $networkSettings);
        }
    }

    private function cloneNetworkSettings(NetworkSettings $original): NetworkSettings
    {
        $cloned = new NetworkSettings();

        $cloned->setNextServer($original->getNextServer());
        $cloned->setBootFileName($original->getBootFileName());
        $cloned->setProxy($original->getProxy());
        $cloned->setDns($original->getDns());
        $cloned->setNetmask($original->getNetmask());
        $cloned->setRouter($original->getRouter());
        $cloned->setP2pTime($original->getP2pTime());
        $cloned->setP2pMode($original->getP2pMode());
        $cloned->setMcastMode($original->getMcastMode());
        $cloned->setMcastIp($original->getMcastIp());
        $cloned->setMcastPort($original->getMcastPort());
        $cloned->setMcastSpeed($original->getMcastSpeed());
        $cloned->setMenu($original->getMenu());
        $cloned->setRepository($original->getRepository());
        $cloned->setOgLive($original->getOgLive());
        $cloned->setPxeTemplate($original->getPxeTemplate());
        $cloned->setNetiface($original->getNetiface());

        return $cloned;
    }

    private function buildNetworkSettings($organizationalUnitEntity): NetworkSettings
    {
        if ($organizationalUnitEntity->getNetworkSettings() === null) {
            $newNetworkSettings = new NetworkSettings();
        } else {
            $newNetworkSettings = $organizationalUnitEntity->getNetworkSettings();
        }

        $newNetworkSettings->setNextServer($organizationalUnitEntity->getNetworkSettings()->getNextServer());
        $newNetworkSettings->setBootFileName($organizationalUnitEntity->getNetworkSettings()->getBootFileName());
        $newNetworkSettings->setProxy($organizationalUnitEntity->getNetworkSettings()->getProxy());
        $newNetworkSettings->setDns($organizationalUnitEntity->getNetworkSettings()->getDns());
        $newNetworkSettings->setNetmask($organizationalUnitEntity->getNetworkSettings()->getNetmask());
        $newNetworkSettings->setRouter($organizationalUnitEntity->getNetworkSettings()->getRouter());
        $newNetworkSettings->setP2pTime($organizationalUnitEntity->getNetworkSettings()->getP2pTime());
        $newNetworkSettings->setP2pMode($organizationalUnitEntity->getNetworkSettings()->getP2pMode());
        $newNetworkSettings->setMcastMode($organizationalUnitEntity->getNetworkSettings()->getMcastMode());
        $newNetworkSettings->setMcastIp($organizationalUnitEntity->getNetworkSettings()->getMcastIp());
        $newNetworkSettings->setMcastPort($organizationalUnitEntity->getNetworkSettings()->getMcastPort());
        $newNetworkSettings->setMcastSpeed($organizationalUnitEntity->getNetworkSettings()->getMcastSpeed());
        $newNetworkSettings->setMenu($organizationalUnitEntity->getNetworkSettings()->getMenu());
        $newNetworkSettings->setRepository($organizationalUnitEntity->getNetworkSettings()->getRepository());
        $newNetworkSettings->setOgLive($organizationalUnitEntity->getNetworkSettings()->getOgLive());
        $newNetworkSettings->setPxeTemplate($organizationalUnitEntity->getNetworkSettings()->getPxeTemplate());
        $newNetworkSettings->setNetiface($organizationalUnitEntity->getNetworkSettings()->getNetiface());

        return $newNetworkSettings;
    }

    /**
     * @throws TransportExceptionInterface
     * @throws ServerExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ClientExceptionInterface
     */
    private function syncOgBoot(OrganizationalUnit $organizationalUnitEntity): void
    {
        $clients = $this->entityManager->getRepository(Client::class)->findClientsByOrganizationalUnitAndDescendants($organizationalUnitEntity->getId(), []);

        if (empty($clients)) {
            return;
        }

        /** @var Client $client */
        foreach ($clients as $client) {
            if ($client->getTemplate() === null) {
                continue;
            }

            $this->postAction->__invoke($client, $client->getTemplate());
        }
    }
}