<?php

namespace App\Auth\Service;

use App\Location\Repository\CityRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Exception\AppException;
use App\Sms\Entity\SmsLog;
use App\Sms\Service\SmsService;
use Symfony\Component\Uid\Uuid;
use Symfony\Contracts\Cache\CacheInterface;

class OtpService
{
    public function __construct(
        private readonly CacheInterface  $cache,
        private readonly SmsService      $sms,
        private readonly CityRepository  $cityRepo,
        private readonly int             $otpTtl = 1200,
        private readonly string          $appEnv = 'dev',
    ) {}

    private function key(string $uuid): string
    {
        // Cache PSR-6 reserved chars: {}()/\@: and - in UUID must be escaped
        return 'otp_' . str_replace('-', '_', $uuid);
    }

    private function grantKey(string $grant): string
    {
        return 'otp_grant_' . $grant;
    }

    public function issueGrant(string $mobile): string
    {
        $grant = bin2hex(random_bytes(32));
        $item  = $this->cache->getItem($this->grantKey($grant));
        $item->set($mobile);
        $item->expiresAfter(120);
        $this->cache->save($item);

        return $grant;
    }

    public function consumeGrant(string $grant): string
    {
        $item = $this->cache->getItem($this->grantKey($grant));

        if (!$item->isHit()) {
            throw new AppException(ErrorCodes::ERR_AUTH_002, null, 400);
        }

        $mobile = $item->get();
        $this->cache->delete($this->grantKey($grant));

        return $mobile;
    }

    public function sendCode(string $mobile, ?string $domain = null): string
    {
        $uuid = Uuid::v4()->toRfc4122();
        $code = $this->appEnv === 'dev'
            ? '12345'
            : str_pad((string) random_int(10000, 99999), 5, '0', STR_PAD_LEFT);

        $item = $this->cache->getItem($this->key($uuid));
        $item->set(json_encode(['mobile' => $mobile, 'code' => $code, 'attempts' => 0, 'verified' => false]));
        $item->expiresAfter($this->otpTtl);
        $this->cache->save($item);

        if ($this->appEnv !== 'dev') {
            $site = $this->resolveSiteName($domain);
            $this->sms->dispatchTemplate(SmsLog::TAG_OTP, $mobile, ['code' => $code, 'site' => $site]);
        }

        return $uuid;
    }

    private function resolveSiteName(?string $domain): string
    {
        $default = 'کلینیک پرو';
        if (!$domain) {
            return $default;
        }

        $domain = strtolower(trim((string) preg_replace('#^https?://#', '', $domain)));
        $domain = (string) preg_replace('#/.*$#', '', $domain);
        $domain = (string) preg_replace('#^www\.#', '', $domain);

        return $this->cityRepo->findOneBy(['domain' => $domain])?->getSiteName() ?: $default;
    }

    public function verifyCode(string $uuid, string $submittedCode): array
    {
        $item = $this->cache->getItem($this->key($uuid));

        if (!$item->isHit()) {
            throw new AppException(ErrorCodes::ERR_AUTH_003, null, 400);
        }

        $data = json_decode($item->get(), true);

        if ($data['attempts'] >= 5) {
            $this->cache->delete($this->key($uuid));
            throw new AppException(ErrorCodes::ERR_AUTH_004, null, 429);
        }

        if (!hash_equals($data['code'], $submittedCode)) {
            $data['attempts']++;
            $item->set(json_encode($data));
            $item->expiresAfter($this->otpTtl);
            $this->cache->save($item);
            throw new AppException(ErrorCodes::ERR_AUTH_002, null, 400);
        }

        $this->cache->delete($this->key($uuid));

        $data['grant'] = $this->issueGrant($data['mobile']);

        return $data;
    }
}
