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


declare(strict_types=1);
namespace BookiFlex\Extensions\Cart\Service;

use BookiFlex\Core\Contract\Fee\FeeCalculatorInterface;
use BookiFlex\Core\Contract\Taxation\TaxCalculatorInterface;
use BookiFlex\Core\Exception\InvalidArgumentException;
use BookiFlex\Extensions\Cart\Contract\CartItemProcessorInterface;
use BookiFlex\Extensions\Cart\Exception\InvalidItemTypeException;
use BookiFlex\Extensions\Cart\Model\AccommodationCartItem;
use BookiFlex\Extensions\Cart\Contract\CartItemInterface;
use BookiFlex\Core\Model\AccommodationType\Accommodation\AccommodationType;
use BookiFlex\Core\Model\RatePlan\RatePlan;
use BookiFlex\Core\Repository\AccommodationTypeRepository;
use BookiFlex\Core\Repository\RatePlanRepository;
use BookiFlex\Core\Service\AvailabilityChecker\AvailabilityChecker;
use BookiFlex\Core\Service\StayCostCalculator\StayCostCalculator;

defined( 'ABSPATH' ) || exit;

/**
 * Processor for accommodation cart items
 */
class AccommodationItemProcessor implements CartItemProcessorInterface
{
    private array $validationErrors = [];

    public function __construct(
        private readonly RatePlanRepository $ratePlanRepository,
        private readonly AccommodationTypeRepository $accommodationTypeRepository,
        private readonly AvailabilityChecker $availabilityChecker,
        private readonly StayCostCalculator $stayCostCalculator,
        private readonly ?TaxCalculatorInterface $taxCalculator = null,
        private readonly ?FeeCalculatorInterface $feeCalculator = null,
        private readonly string $checkInTime = '14:00'
    ) {
    }

    /**
     * Create accommodation item from request data
     */
    public function createItem(array $data): CartItemInterface
    {
        return AccommodationCartItem::fromRequest($data);
    }

    /**
     * Validate accommodation item
     */
    public function validate(CartItemInterface $item, int $quantity = 1): bool
    {
        if (!$item instanceof AccommodationCartItem) {
            $this->validationErrors['invalid_item_type'] = 'Invalid item type';
            return false;
        }

        $this->validationErrors = [];

        // Basic validation
        if (!$item->validate()) {
            $this->validationErrors = array_merge($this->validationErrors, $item->getValidationErrors());
        }

        $this->checkAvailability($item, $quantity);
        if (!$this->availabilityChecker->isAvailable()) {
            $this->validationErrors = array_merge($this->validationErrors, $this->availabilityChecker->getErrorMessages());
        }

        return empty($this->validationErrors);
    }

    /**
     * Get validation errors as associative array
     *
     * @return array<string, string> Associative array where key is error code and value is error message
     */
    public function getValidationErrors(): array
    {
        return $this->validationErrors;
    }

    /**
     * Process accommodation item (calculate prices, apply taxes)
     */
    public function process(CartItemInterface $item): CartItemInterface
    {
        if (!$item instanceof AccommodationCartItem) {
            throw new InvalidItemTypeException(
                'Invalid item type',
                400,
                null,
                ['expected' => AccommodationCartItem::class, 'actual' => get_class($item)]
            );
        }

        // Get entities
        /** @var RatePlan $ratePlan */
        $ratePlan = $this->ratePlanRepository->findByPK(
            $item->getAccommodationSelection()->getRatePlanId()
        );
        /** @var AccommodationType $accommodationType */
        $accommodationType = $this->accommodationTypeRepository->findByPK(
            $item->getAccommodationSelection()->getAccommodationTypeId()
        );

        // Set names
        $item->setNames(
            $accommodationType->getName(),
            $ratePlan->getName()
        );

        // Set cancellation policy
        $cancellationPolicy = $ratePlan->getCancellationPolicy();
        $checkInDateTime = new \DateTimeImmutable(
            $item->getStayPeriod()->getCheckInDate()->format('Y-m-d') . ' ' . $this->checkInTime
        );

        $item->setCancellationPolicy(
            $cancellationPolicy->getId(),
            $cancellationPolicy->getName(),
            $cancellationPolicy->getCancellationConsequences($checkInDateTime)
        );

        // Set default payment type if not set
        if ($item->getPaymentTypeId() === 0 && count($ratePlan->getPaymentTypes()) > 0) {
            $paymentTypes = $ratePlan->getAvailablePaymentTypes($checkInDateTime);
            $defaultPaymentType = $paymentTypes[0];
            $item->setPaymentType(
                $defaultPaymentType->getId(),
                $defaultPaymentType->getName()
            );
        }

        // Calculate stay cost
        return $this->calculateItemTotal($item);
    }

    /**
     * Calculate and set latest price for the item
     * @param AccommodationCartItem $item
     * @throws InvalidArgumentException
     */
    public function calculateItemTotal(CartItemInterface $item): CartItemInterface
    {
        $stayCostResult = $this->stayCostCalculator->calculate(
            $item->getStayPeriod(),
            $item->getAccommodationSelection(),
            $item->getGuestConfiguration()
        );

        $taxableObjects = [$stayCostResult];
        foreach ($stayCostResult->getDailyCosts() as $dailyCost) {
            $taxableObjects[] = $dailyCost;
        }

        $this->taxCalculator?->applyTaxes($taxableObjects);
        $this->feeCalculator?->applyFees($taxableObjects);

        $item->setStayCostResult($stayCostResult);

        return $item;
    }

    /**
     * Check availability for accommodation
     */
    public function checkAvailability(CartItemInterface $item, int $quantity): bool
    {
        if (!$item instanceof AccommodationCartItem) {
            return false;
        }

        $this->availabilityChecker->check(
            $item->getStayPeriod(),
            $item->getAccommodationSelection(),
            $item->getGuestConfiguration(),
            $quantity,
            $this->checkInTime
        );

        return $this->availabilityChecker->isAvailable();
    }
}
