<?php

declare(strict_types=1);

namespace App\Service;

use App\Entity\Signal;
use App\Exception\SubmissionRateLimitedException;
use App\Message\SignalCreatedMessage;
use App\Payload\SignalPayload;
use App\Repository\SignalRepository;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\RateLimiter\RateLimiterFactory;

final readonly class SignalSubmissionService
{
    public function __construct(
        private SignalRepository $signals,
        private MessageBusInterface $messageBus,
        #[Autowire(service: 'limiter.signal_submission')] private RateLimiterFactory $submissionLimiter,
        private PointProximityValidator $proximityValidator,
        #[Autowire(service: 'monolog.logger.signals')] private LoggerInterface $logger,
    ) {
    }

    public function submit(string $clientKey, SignalPayload $payload): Signal
    {
        $this->enforceRateLimit($clientKey);
        $signalLocation = $payload->signalLocation;
        $userLocation = $payload->userLocation;

        $this->proximityValidator->assertWithinRange($userLocation, $signalLocation);

        $this->logger->info('Storing new signal submission.', [
            'client_key' => $clientKey,
            'signal_location' => [
                'lat' => round($signalLocation->getLat(), 5),
                'lng' => round($signalLocation->getLng(), 5),
            ],
            'user_location' => [
                'lat' => round($userLocation->getLat(), 5),
                'lng' => round($userLocation->getLng(), 5),
            ],
        ]);

        $signal = Signal::create($clientKey, $userLocation, $signalLocation);
        $this->signals->save($signal);

        $this->logger->debug('Dispatching signal created message.', [
            'signal_id' => $signal->id->toString(),
        ]);
        $this->messageBus->dispatch(new SignalCreatedMessage($signal->id));
        $this->logger->info('Signal stored successfully.', [
            'signal_id' => $signal->id->toString(),
            'client_key' => $clientKey,
        ]);

        return $signal;
    }

    private function enforceRateLimit(string $clientKey): void
    {
        $rateLimiter = $this->submissionLimiter->create($clientKey);
        $limit = $rateLimiter->consume(1);

        if (! $limit->isAccepted()) {
            $retryAfter = $limit->getRetryAfter();
            $this->logger->warning('Signal submission rejected due to rate limiting.', [
                'client_key' => $clientKey,
                'retry_after' => $retryAfter->format(DATE_ATOM),
            ]);
            throw new SubmissionRateLimitedException($limit->getRetryAfter());
        }

        $this->logger->debug('Signal submission passed rate limiting.', [
            'client_key' => $clientKey,
            'remaining_tokens' => $limit->getRemainingTokens(),
        ]);
    }
}
