<?php declare(strict_types=1);

namespace App\Services\Api\V1;

use App\Dto\Request\Api\V1\Captcha\CaptchaPublicToken;
use App\Dto\Request\Api\V1\Captcha\CheckingDto;
use App\Dto\Request\Api\V1\Captcha\VerificationInformationDto;
use App\Enums\CaptchaLogType;
use App\Repositories\CaptchaLogRepository;
use App\Repositories\CaptchaRepository;
use App\Repositories\DataCaptchaRepository;
use App\ServiceResults\Api\V1\CaptchaService\Captcha;
use App\ServiceResults\Api\V1\CaptchaService\CaptchaVerificationInformationResult;
use App\ServiceResults\Api\V1\CaptchaService\CaptchaVerifiedResult;
use App\ServiceResults\ServiceResultError;
use App\Services\Captcha\CaptchaHandler;
use App\Services\Captcha\CheckingCommand;
use App\Services\CaptchaLog\CaptchaLogHandler;
use App\Services\Service;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

final class CaptchaService extends Service
{
    public function __construct(
        private readonly CaptchaGenerateService $captchaGenerateService,
        private readonly CaptchaHandler $captchaHandler,
        private readonly CaptchaLogHandler $captchaLogHandler,
        private readonly DataCaptchaRepository $dataCaptchaRepository,
        private readonly CaptchaLogRepository $captchaLogRepository,
        private readonly CaptchaRepository $captchaRepository,
        private readonly CheckingCommand $checkingCommand,
    ) { }

    public function createKeyWithCaptcha(CaptchaPublicToken $captchaPublicToken, Carbon $expires): ServiceResultError | Captcha
    {
        try {
            $captcha = $this->captchaGenerateService->generate();
            if ($captcha->isError()) {
                return $captcha;
            }

            $modelCaptcha = DB::transaction(function () use ($captchaPublicToken) {
                return $this->captchaHandler->handleStore($captchaPublicToken->getCaptchaToken(), $captchaPublicToken->getHttpUserData());
            });

            $captchaKey = $this->dataCaptchaRepository->store($modelCaptcha, $captcha->getImageBody()->getCoordinators(), $expires);
        } catch (\Throwable $e) {
            report($e);
            return $this->errService('Captcha service error!');
        }

        return new Captcha(
            imageHead: $captcha->getImageHead()->getImage(),
            imageBody: $captcha->getImageBody()->getImage(),
            key: $captchaKey
        );
    }

    public function checking(CheckingDto $checkingDto, int $maxCountError): ServiceResultError | CaptchaVerifiedResult
    {
        try {
            $captchaData = $this->dataCaptchaRepository->getByKey($checkingDto->getCaptchaKey());
            if (is_null($captchaData)) {
                return $this->errValidate(__('Captcha not found or verification period has expired. Please try to refresh the captcha.'));
            }
            if (!$this->checkingCommand->execute($captchaData->getCoordinators(), $checkingDto->getCoordinators())) {
                $this->captchaLogHandler->handleStore($captchaData->getCaptchaId(), CaptchaLogType::Error, $checkingDto->getCaptchaPublicToken()->getHttpUserData());
                return $this->errValidate(__('CAPTCHA validation failed. Please try again.'));
            }
            $result = DB::transaction(function () use ($checkingDto, $captchaData, $maxCountError) {
                $errorCount = $this->captchaLogRepository->countByType(CaptchaLogType::Error, $captchaData->getCaptchaId());
                if ($errorCount > $maxCountError) {
                    $this->dataCaptchaRepository->destroy($checkingDto->getCaptchaKey());
                    return $this->errValidate(__('You have exceeded the number of attempts. Please refresh the captcha.'));
                }

                $this->captchaLogHandler->handleStore($captchaData->getCaptchaId(), CaptchaLogType::Verified, $checkingDto->getCaptchaPublicToken()->getHttpUserData());
                $this->dataCaptchaRepository->destroy($checkingDto->getCaptchaKey());

                $modelCaptcha = $this->captchaRepository->getCaptchaById($captchaData->getCaptchaId());
                if (is_null($modelCaptcha)) {
                    return $this->errValidate(__('Captcha not found or verification period has expired. Please try to refresh the captcha.'));
                }

                return new CaptchaVerifiedResult(key: $modelCaptcha->uuid);
            });
        } catch (\Throwable $e) {
            report($e);
            return $this->errService('Captcha service error!');
        }

        return $result;
    }

    public function verificationInformation(string $captchaUuid, VerificationInformationDto $verificationInformationDto, int $expiresMinutes, int $maxInfoDisplayCount = 1): ServiceResultError | CaptchaVerificationInformationResult
    {
        try {
            $captcha = $this->captchaRepository->getCaptchaByUuid($verificationInformationDto->getCaptchaToken(), $captchaUuid);
            if (is_null($captcha)) {
                return $this->errNotFound(__('Captcha not found'));
            }

            $captchaLogs = $this->captchaLogRepository->getCaptchaLogsByTypes([CaptchaLogType::Verified, CaptchaLogType::ReadVerified], $captcha->id);
            $this->captchaLogHandler->handleStore($captcha->id, CaptchaLogType::ReadVerified, $verificationInformationDto->getHttpUserData());

            $captchaVerificationLog = $captchaLogs->firstWhere('type', CaptchaLogType::Verified);
            if (is_null($captchaVerificationLog)) {
                return new CaptchaVerificationInformationResult(
                    status: false,
                    message: __('Captcha does not pass verification'),
                );
            }

            if (!is_null($verificationInformationDto->getUserAgent()) && Str::limit($verificationInformationDto->getUserAgent(), 255, '') !== $captchaVerificationLog->user_agent) {
                return new CaptchaVerificationInformationResult(
                    status: false,
                    message: __('Captcha User Agent does not match'),
                );
            }

            if (now()->diffInMinutes($captchaVerificationLog->created_at) > $expiresMinutes) {
                return new CaptchaVerificationInformationResult(
                    status: false,
                    message: __('The time for captcha verification has passed'),
                );
            }

            if ($captchaLogs->where('type', CaptchaLogType::ReadVerified)->count() >= $maxInfoDisplayCount) {
                return new CaptchaVerificationInformationResult(
                    status: false,
                    message: __('Captcha verification status view count exceeded'),
                );
            }
        } catch (\Throwable $e) {
            report($e);
            return $this->errService('Captcha service error!');
        }

        return new CaptchaVerificationInformationResult(
            status: true,
            message: __('Captcha Verified'),
        );
    }
}
