<?php

declare(strict_types=1);

/*
 * This file is part of the NovoSGA project.
 *
 * (c) Rogerio Lino <rogeriolino@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace App\Service;

use App\Entity\Metadata;
use Novosga\Entity\UsuarioInterface;
use Novosga\Repository\MetadataRepositoryInterface;
use Novosga\Repository\UsuarioMetadataRepositoryInterface;
use Novosga\Service\ApplicationSettingsServiceInterface;
use Novosga\Settings\AppearanceSettings;
use Novosga\Settings\ApplicationSettings;
use Novosga\Settings\BehaviorSettings;
use Novosga\Settings\QueueSettings;
use Novosga\Settings\UserBehaviorSettings;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

/**
 * ApplicationSettingsService
 *
 * @author Rogerio Lino <rogeriolino@gmail.com>
 */
class ApplicationSettingsService implements ApplicationSettingsServiceInterface
{
    private const APP_NAMESPACE = 'novosga.settings';
    private const APP_APPEARANCE = 'appearance';
    private const APP_BEHAVIOR = 'behavior';
    private const APP_QUEUE = 'queue';

    private ?ApplicationSettings $settings = null;

    public function __construct(
        private readonly NormalizerInterface $normalizer,
        private readonly DenormalizerInterface $denormalizer,
        private readonly MetadataRepositoryInterface $metadataRepository,
        private readonly UsuarioMetadataRepositoryInterface $usuarioMetadataRepository,
    ) {
    }

    public function loadSettings(): ApplicationSettings
    {
        return $this->settings ??= $this->doLoadSettings();
    }

    public function loadAppearanceSettings(): AppearanceSettings
    {
        return $this->getMetadataValue(self::APP_APPEARANCE, AppearanceSettings::class);
    }

    public function loadQueueSettings(): QueueSettings
    {
        return $this->getMetadataValue(self::APP_QUEUE, QueueSettings::class);
    }

    public function loadBehaviorSettings(): BehaviorSettings
    {
        return $this->getMetadataValue(self::APP_BEHAVIOR, BehaviorSettings::class);
    }

    public function saveSettings(ApplicationSettings $settings): void
    {
        $this->saveAppearanceSettings($settings->appearance);
        $this->saveBehaviorSettings($settings->behavior);
        $this->saveQueueSettings($settings->queue);
    }

    public function saveAppearanceSettings(AppearanceSettings $settings): void
    {
        $this->setMetadataValue(self::APP_APPEARANCE, $settings);
    }

    public function saveBehaviorSettings(BehaviorSettings $settings): void
    {
        $this->setMetadataValue(self::APP_BEHAVIOR, $settings);
    }

    public function saveQueueSettings(QueueSettings $settings): void
    {
        $this->setMetadataValue(self::APP_QUEUE, $settings);
    }

    public function loadUserBehaviorSettings(
        UsuarioInterface $usuario,
        ?bool $resolveGlobal = true,
    ): UserBehaviorSettings {
        $global = $resolveGlobal ? $this->loadBehaviorSettings() : null;
        $meta = $this->usuarioMetadataRepository->get($usuario, self::APP_NAMESPACE, self::APP_BEHAVIOR);
        $value = $meta ? $meta->getValue() : [];

        return new UserBehaviorSettings(
            callTicketByService: $value['callTicketByService'] ?? $global?->callTicketByService,
            callTicketOutOfOrder: $value['callTicketOutOfOrder'] ?? $global?->callTicketOutOfOrder,
            changeTicketType: $value['changeTicketType'] ?? $global?->changeTicketType,
        );
    }

    public function saveUserBehaviorSettings(UsuarioInterface $usuario, UserBehaviorSettings $settings): void
    {
        $this->usuarioMetadataRepository->set(
            $usuario,
            self::APP_NAMESPACE,
            self::APP_BEHAVIOR,
            [
                'callTicketByService' => $settings->callTicketByService,
                'callTicketOutOfOrder' => $settings->callTicketOutOfOrder,
                'changeTicketType' => $settings->changeTicketType,
            ],
        );
    }

    private function doLoadSettings(): ApplicationSettings
    {
        return new ApplicationSettings(
            appearance: $this->loadAppearanceSettings(),
            behavior: $this->loadBehaviorSettings(),
            queue: $this->loadQueueSettings(),
        );
    }

    /**
     * @template T
     * @class-name T
     * @param class-string<T> $type
     * @return T
     */
    private function getMetadataValue(string $name, string $type): mixed
    {
        /** @var Metadata|null */
        $metadata = $this->metadataRepository->findOneBy([
            'namespace' => self::APP_NAMESPACE,
            'name' => $name,
        ]);

        return $this->denormalizer->denormalize($metadata?->getValue(), $type);
    }

    private function setMetadataValue(string $name, mixed $settings): void
    {
        $value = $this->normalizer->normalize($settings);
        $this->metadataRepository->set(
            self::APP_NAMESPACE,
            $name,
            $value,
        );
    }
}
