<?php
/**
 * BookiFlex Core
 *
 * @package    BookiFlex
 * @subpackage Core
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

namespace BookiFlex\Core\Service\DateUnit;

use BookiFlex\Core\Exception\InvalidArgumentException;
use BookiFlex\Core\Model\AccommodationType\Accommodation\AccommodationType;
use BookiFlex\Core\Model\DateUnit\DateUnit;
use BookiFlex\Core\Model\RatePlan\RatePlan;
use BookiFlex\Core\Repository\DateUnitRepository;
use BookiFlex\Core\Service\DateUnit\DTO\ExtendDateUnits as ExtendDateUnitsDTO;
use BookiFlex\Core\Util\DateRange\DateRange;
use BookiFlex\Core\Util\DateRange\DateRangeCreator;
use Cycle\ORM\EntityManagerInterface;
use DateTimeInterface;
use DateTimeImmutable;

defined( 'ABSPATH' ) || exit;

/**
 * Create DateUnit for current rate plan and accommodation types by given date range.
 * New DateUnits will be created only for all days but have restriction STOP_SALE.
 */
class DateUnitExtender
{
    private array $allowToUpdateDays = [
        'mon' => false,
        'tue' => false,
        'wed' => false,
        'thu' => false,
        'fri' => false,
        'sat' => false,
        'sun' => false
    ];

    public function __construct(
        private readonly DateRangeCreator $unitsRangeCreator,
        private readonly GetMinMaxDates $getMinMaxDates,
        private readonly DateUnitRepository $dateUnitRepository,
        private readonly EntityManagerInterface $entityManager
    ) {
    }

    /**
     * Create DateUnit for current rate plan.
     * If rate plan is children, then DateUnits are created for parent first and then for child.
     *
     * @param ExtendDateUnitsDTO $command
     * @return DateUnit[]
     */
    public function handle(ExtendDateUnitsDTO $command): array
    {
        $ratePlan = $command->getRatePlan();
        $this->setAllowedToUpdateDays($command->getActiveDays());

        $units = [];

        // If rate plan has parent then update parent too
        if ($ratePlan->isChildRatePlan()) {
            $this->handleParentRatePlan($command);
            // @todo: has potential to be refactored
            // Note: parent units are already added to database by this time
            // $units = array_merge($units, $parentUnits);
        }

        // Create units for each accommodation type
        foreach ($command->getAccommodationTypes() as $accommodationType) {
            $unitsForAccommodation = $this->createUnitsForAccommodationType(
                $ratePlan,
                $accommodationType,
                $command->getDateFrom(),
                $command->getDateTo()
            );

            $units = array_merge($units, $unitsForAccommodation);
        }

        // Persist all units
        $this->persistUnits($units);

        return $units;
    }

    /**
     * Handle parent rate plan update
     */
    private function handleParentRatePlan(ExtendDateUnitsDTO $command): void
    {
        $parentCommand = new ExtendDateUnitsDTO(
            $command->getRatePlan()->getParent(),
            $command->getAccommodationTypes(),
            $command->getDateFrom(),
            $command->getDateTo(),
            $command->getActiveDays()
        );

        $this->handle($parentCommand);
    }

    /**
     * Create units for specific accommodation type
     */
    private function createUnitsForAccommodationType(
        RatePlan $ratePlan,
        AccommodationType $accommodationType,
        DateTimeImmutable $dateFrom,
        DateTimeImmutable $dateTo
    ): array {
        try {
            $prolongedRanges = $this->calculateProlongedRanges(
                $ratePlan,
                $accommodationType,
                $dateFrom,
                $dateTo
            );
        } catch (InvalidArgumentException $e) {
            // todo: add to log
            return [];
        }

        return $this->makeUnits($ratePlan, $accommodationType, $prolongedRanges);
    }

    /**
     * Calculate prolonged date ranges for creating units
     *
     * @throws InvalidArgumentException
     */
    private function calculateProlongedRanges(
        RatePlan $ratePlan,
        AccommodationType $accommodationType,
        DateTimeImmutable $dateFrom,
        DateTimeImmutable $dateTo
    ): array {
        $minMaxDate = $this->getMinMaxDates->getMinMaxDates(
            $ratePlan->getId(),
            $accommodationType->getId()
        );

        return $this->unitsRangeCreator->create(
            $dateFrom->format('Y-m-d'),
            $dateTo->format('Y-m-d'),
            $minMaxDate['min'],
            $minMaxDate['max']
        );
    }

    /**
     * Create DateUnit instances for given ranges
     *
     * @param RatePlan $ratePlan
     * @param AccommodationType $accommodationType
     * @param DateRange[] $prolongedRanges
     * @return DateUnit[]
     */
    protected function makeUnits(
        RatePlan $ratePlan,
        AccommodationType $accommodationType,
        array $prolongedRanges
    ): array {
        $units = [];

        foreach ($prolongedRanges as $rangeIterator) {
            $rangeUnits = $this->createUnitsForRange(
                $ratePlan,
                $accommodationType,
                $rangeIterator
            );

            if ($ratePlan->isChildRatePlan() && count($rangeUnits)) {
                $this->linkWithParentUnits(
                    $ratePlan,
                    $accommodationType,
                    $rangeIterator,
                    $rangeUnits
                );
            }

            $units = array_merge($units, array_values($rangeUnits));
        }

        return $units;
    }

    /**
     * Create units for specific date range
     */
    private function createUnitsForRange(
        RatePlan $ratePlan,
        AccommodationType $accommodationType,
        DateRange $rangeIterator
    ): array {
        $units = [];

        /** @var DateTimeImmutable $date */
        foreach ($rangeIterator as $date) {
            $key = $date->format('Y-m-d');
            $dateUnit = new DateUnit($ratePlan->getId(), $accommodationType->getId(), $date);
            $dateUnit->setStopSale(true);
            // $dateUnit->setStopSale(!$this->isAllowedToUpdateDay($date));

            $units[$key] = $dateUnit;
        }

        return $units;
    }

    /**
     * Link child units with parent units
     */
    private function linkWithParentUnits(
        RatePlan $ratePlan,
        AccommodationType $accommodationType,
        DateRange $rangeIterator,
        array &$units
    ): void {
        $parentUnits = $this->dateUnitRepository->findByRange(
            $rangeIterator->getDateFrom(),
            $rangeIterator->getDateTo(),
            $ratePlan->getParent()->getId(),
            $accommodationType->getId()
        );

        foreach ($units as $dateStr => $unit) {
            if (isset($parentUnits[$dateStr])) {
                $unit->setParentDateUnit(
                    $parentUnits[$dateStr],
                    $ratePlan->getInheritanceRestrictionRules(),
                    $ratePlan->getInheritancePriceRules()
                );
            }
        }
    }

    /**
     * Check if updates are allowed for specific day of week
     */
    protected function isAllowedToUpdateDay(DateTimeInterface $date): bool
    {
        $dayOfWeek = strtolower($date->format('D'));
        return array_key_exists($dayOfWeek, $this->allowToUpdateDays)
            && $this->allowToUpdateDays[$dayOfWeek];
    }

    /**
     * Set allowed days for update
     */
    private function setAllowedToUpdateDays(array $activeDays): void
    {
        $this->allowToUpdateDays = array_merge($this->allowToUpdateDays, $activeDays);
    }

    /**
     * Persist all units to database
     */
    private function persistUnits(array $units): void
    {
        foreach ($units as $unit) {
            $this->entityManager->persist($unit);
        }
        $this->entityManager->run();
    }
}
