<?php

namespace App\Resource\Service;

use App\Representation\Service\JalaliDateService;
use App\Resource\Entity\NationalHoliday;
use App\Resource\Entity\TenantHolidayOverride;
use App\Resource\Repository\NationalHolidayRepository;
use App\Resource\Repository\TenantHolidayOverrideRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Exception\AppException;
use Doctrine\ORM\EntityManagerInterface;

/**
 * تعطیلات رسمی کشور و استثناهای هر محیط رویشان.
 *
 * تبدیل شمسی از {@see JalaliDateService} می‌آید و اینجا دوباره پیاده نمی‌شود — آن
 * سرویس از فاز نمایندگی موجود است و درست کار می‌کند. (نامِ فضایش
 * `App\Representation` است که برای مصرف مشترک جای درستی نیست؛ جابه‌جا کردنش یک
 * تغییر مکانیکی در کل کدبیس است و به این تسک ربطی ندارد.)
 */
final class HolidayService
{
    /** تهران مرجع «روز» است: تعطیل رسمی کشوری است، نه محلیِ شعبه. */
    public const NATIONAL_TIMEZONE = 'Asia/Tehran';

    public function __construct(
        private readonly NationalHolidayRepository       $holidays,
        private readonly TenantHolidayOverrideRepository $overrides,
        private readonly JalaliDateService               $jalali,
        private readonly EntityManagerInterface          $em,
    ) {}

    /** نیمه‌شبِ یک تاریخ شمسی به وقت تهران. */
    public function jalaliToMidnight(int $jy, int $jm, int $jd): int
    {
        [$gy, $gm, $gd] = $this->jalali->jalaliToGregorian($jy, $jm, $jd);

        return (new \DateTimeImmutable(
            sprintf('%04d-%02d-%02d 00:00:00', $gy, $gm, $gd),
            new \DateTimeZone(self::NATIONAL_TIMEZONE),
        ))->getTimestamp();
    }

    /**
     * یک تعطیل رسمی را ثبت یا به‌روز می‌کند. idempotent: تکیه‌گاهش `date` است که
     * یکتاست، پس import دوباره ردیف تکراری نمی‌سازد.
     *
     * خودش flush می‌کند و این عمدی است: سپردنِ flush به فراخوان یعنی اگر او
     * EntityManager دیگری در دست داشته باشد، persist روی یکی و flush روی دیگری
     * می‌افتد و **هیچ ردیفی نوشته نمی‌شود، بی‌هیچ خطایی**. همین در تست‌ها اتفاق افتاد،
     * چون هر درخواست HTTP کرنل را از نو می‌سازد و سرویس از کانتینر تازه می‌آید.
     */
    public function upsertNational(int $jy, int $jm, int $jd, string $title): NationalHoliday
    {
        $date     = $this->jalaliToMidnight($jy, $jm, $jd);
        $existing = $this->holidays->findByDate($date);

        if ($existing !== null) {
            $existing->setTitle($title);
            $this->em->flush();

            return $existing;
        }

        $holiday = new NationalHoliday(
            $date,
            sprintf('%04d-%02d-%02d', $jy, $jm, $jd),
            $jy,
            $title,
        );

        $this->em->persist($holiday);
        $this->em->flush();

        return $holiday;
    }

    /**
     * همهٔ تعطیلات یک سال را پاک می‌کند — تنها راه تصحیحِ ردیفی که با تاریخ غلط
     * نوشته شده، چون upsert روی `date` کلید می‌خورد و ردیف غلط را نمی‌شناسد.
     */
    public function purgeYear(int $jalaliYear): int
    {
        return (int) $this->em->createQuery(
            'DELETE FROM App\Resource\Entity\NationalHoliday h WHERE h.jalaliYear = :year'
        )->setParameter('year', $jalaliYear)->execute();
    }

    /** @return NationalHoliday[] */
    public function forYear(int $jalaliYear): array
    {
        return $this->holidays->findForYear($jalaliYear);
    }

    /**
     * استثنای محیط. روی همان تاریخ دوباره فرستادن، همان ردیف را عوض می‌کند — وگرنه
     * کلید یکتا با خطای خام دیتابیس می‌شکست.
     */
    public function setOverride(string $entityType, int $entityId, int $date, bool $isWorking, ?string $note): TenantHolidayOverride
    {
        $midnight = $this->midnightTehran($date);
        $existing = $this->overrides->findForDate($entityType, $entityId, $midnight);

        if ($existing !== null) {
            $existing->setWorking($isWorking)->setNote($note);
            $this->em->flush();

            return $existing;
        }

        $override = new TenantHolidayOverride($entityType, $entityId, $midnight, $isWorking);
        $override->setNote($note);

        $this->em->persist($override);
        $this->em->flush();

        return $override;
    }

    public function deleteOverride(TenantHolidayOverride $override): void
    {
        $this->em->remove($override);
        $this->em->flush();
    }

    /** @return TenantHolidayOverride[] */
    public function overridesForYear(string $entityType, int $entityId, int $jalaliYear): array
    {
        $from = $this->jalaliToMidnight($jalaliYear, 1, 1);
        $to   = $this->jalaliToMidnight($jalaliYear + 1, 1, 1);

        return array_values($this->overrides->mapForRange($entityType, $entityId, $from, $to));
    }

    public function assertJalaliYear(mixed $value): int
    {
        if (!is_numeric($value) || (int) $value < 1300 || (int) $value > 1500) {
            throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'سال شمسی نامعتبر است', 422, 'year');
        }

        return (int) $value;
    }

    private function midnightTehran(int $timestamp): int
    {
        return (new \DateTimeImmutable('@' . $timestamp))
            ->setTimezone(new \DateTimeZone(self::NATIONAL_TIMEZONE))
            ->setTime(0, 0)
            ->getTimestamp();
    }
}
