<?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\Model\Reservation;

use BookiFlex\Core\Exception\MoneyCalculationException;
use BookiFlex\Core\Service\CashierFactory;
use Brick\Money\Money;
use DateTimeImmutable;

defined( 'ABSPATH' ) || exit;

/**
 * Represents an extra service item in a reservation.
 *
 * Extra items can include:
 * - Additional services (breakfast, parking, spa, etc.)
 * - Equipment rental (bikes, cribs, etc.)
 * - Special packages or experiences
 * - Transfer services
 *
 * Items can be:
 * - Refundable: Can be refunded if reservation is cancelled
 * - Neutral: Don't affect total price (complimentary/included items)
 *
 * @package BookiFlex\Core\Model\Reservation
 */
class ReservationExtraItem extends AbstractReservationItem
{
    /** @var bool Whether this item can be refunded on cancellation */
    private bool $refundable = true;

    /** @var bool Whether this item is complimentary (doesn't affect total) */
    private bool $neutral = false;

    /**
     * Create a new extra service item.
     *
     * @param string $name Service name/description
     * @param Money $price Unit price for the service
     * @param int $quantity Number of units
     * @param string $code Service code identifier (e.g., 'BRK', 'PARK', 'SPA')
     * @param DateTimeImmutable|null $date Service date (null for stay-long services)
     *
     * @throws MoneyCalculationException If price calculation fails
     */
    public function __construct(
        string $name,
        Money $price,
        private int $quantity,
        private readonly string $code,
        private readonly ?DateTimeImmutable $date = null
    ) {
        parent::__construct($name, $price);

        // Ensure quantity is at least 1
        $this->quantity = max(1, $quantity);

        // Recalculate total with quantity
        $this->recalculateTotalPrice();
    }

    // ========================================================================
    // Specific Properties Access
    // ========================================================================

    /**
     * Get service date.
     *
     * @return DateTimeImmutable|null Service date or null for stay-long services
     */
    public function getDate(): ?DateTimeImmutable
    {
        return $this->date;
    }

    /**
     * Get service code identifier.
     *
     * @return string Service code (e.g., 'BRK', 'PARK')
     */
    public function getCode(): string
    {
        return $this->code;
    }

    /**
     * Get quantity of service units.
     *
     * @return int Number of units (always >= 1)
     */
    public function getQuantity(): int
    {
        return $this->quantity;
    }

    /**
     * Update quantity of service units.
     *
     * @param int $quantity New quantity (will be set to 1 if less)
     * @return $this
     */
    public function setQuantity(int $quantity): self
    {
        $this->quantity = max(1, $quantity);
        $this->recalculateTotalPrice();
        return $this;
    }

    // ========================================================================
    // Refundable/Neutral Properties
    // ========================================================================

    /**
     * Check if item is refundable on cancellation.
     *
     * @return bool True if can be refunded
     */
    public function isRefundable(): bool
    {
        return $this->refundable;
    }

    /**
     * Set whether item is refundable.
     * Non-refundable items are included in cancellation fee calculations.
     *
     * @param bool $value True if refundable
     * @return $this
     */
    public function setRefundable(bool $value): self
    {
        $this->refundable = $value;
        return $this;
    }

    /**
     * Check if item is neutral (complimentary/included).
     * Neutral items don't affect the reservation total.
     *
     * @return bool True if complimentary
     */
    public function isNeutral(): bool
    {
        return $this->neutral;
    }

    /**
     * Set whether item is neutral (complimentary).
     *
     * @param bool $value True if complimentary
     * @return $this
     */
    public function setNeutral(bool $value): self
    {
        $this->neutral = $value;
        return $this;
    }

    // ========================================================================
    // Price Calculation Overrides
    // ========================================================================

    /**
     * Get base price considering quantity.
     * Returns the total base price (unit price × quantity).
     *
     * @return Money Total base price
     * @throws MoneyCalculationException If calculation fails
     */
    public function getBasePrice(): Money
    {
        $unitPrice = parent::getBasePrice();
        return $unitPrice->multipliedBy($this->quantity);
    }

    /**
     * Get unit price (price for single item).
     *
     * @return Money Price per unit
     * @throws MoneyCalculationException If calculation fails
     */
    public function getUnitPrice(): Money
    {
        return parent::getBasePrice();
    }

    // ========================================================================
    // Helper Methods
    // ========================================================================

    /**
     * Check if this service is for a specific date.
     *
     * @param DateTimeImmutable $date Date to check
     * @return bool True if dates match or service has no specific date
     */
    public function isForDate(DateTimeImmutable $date): bool
    {
        if ($this->date === null) {
            // Service applies to entire stay
            return true;
        }

        return $this->date->format('Y-m-d') === $date->format('Y-m-d');
    }

    /**
     * Check if this is a date-specific service.
     *
     * @return bool True if service has specific date
     */
    public function hasSpecificDate(): bool
    {
        return $this->date !== null;
    }

    /**
     * Calculate total value including quantity.
     *
     * @return Money Total value (base price × quantity + adjustments)
     * @throws MoneyCalculationException If calculation fails
     */
    public function calculateTotalValue(): Money
    {
        if ($this->isNeutral()) {
            return Money::zero($this->currency);
        }

        return $this->getTotalPrice();
    }

    // ========================================================================
    // Protected Methods Override
    // ========================================================================

    /**
     * Recalculate total price including quantity and adjustments.
     */
    protected function recalculateTotalPrice(): void
    {
        $this->invalidateCachedSummary();

        // Get base total (unit price × quantity)
        $baseTotal = $this->getBasePrice();

        // Apply adjustments through cashier
        $this->cachedSummary = CashierFactory::make($baseTotal, $this->adjustments);
        $this->totalPrice = $this->cachedSummary->total()->getMinorAmount()->toInt();
    }
}
