<?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\RuntimeException;
use BookiFlex\Core\Model\Identifiable;
use BookiFlex\Core\Model\Timestamps;
use BookiFlex\Core\Util\DateTimeUtils;

defined( 'ABSPATH' ) || exit;

/**
 * Represents a group of related reservations.
 *
 * Groups are used for:
 * - Travel groups booking multiple rooms
 * - Corporate bookings with multiple reservations
 * - Events requiring multiple accommodations
 * - Split reservations (when original reservation is divided)
 *
 * The group status is automatically calculated based on individual reservation statuses
 * with priority given to the most critical states (overdue > cancelled > confirmed > active).
 *
 * @package BookiFlex\Core\Model\Reservation
 */
class GroupReservation
{
    use Timestamps;
    use Identifiable;

    /** @var GroupReservationStatus Current group status */
    private GroupReservationStatus $status;

    /** @var Reservation[] All reservations in the group */
    private array $reservations = [];

    /**
     * Create a new reservation group.
     */
    public function __construct()
    {
        $this->status = new GroupReservationStatus(GroupReservationStatus::STATUS_ACTIVE);
        $this->createdAt = DateTimeUtils::nowUtc();
    }

    // ========================================================================
    // Status Management
    // ========================================================================

    /**
     * Get current group status.
     *
     * @return GroupReservationStatus
     */
    public function getStatus(): GroupReservationStatus
    {
        return $this->status;
    }

    /**
     * Update group status based on member reservation statuses.
     *
     * Status priority (highest to lowest):
     * 1. OVERDUE - All reservations overdue
     * 2. PARTIALLY_OVERDUE - At least one overdue
     * 3. CANCELLED - All reservations cancelled
     * 4. CONFIRMED - All reservations confirmed
     * 5. PARTIALLY_CONFIRMED - At least one confirmed
     * 6. ACTIVE - Default/mixed status
     *
     * @return $this
     * @throws RuntimeException
     */
    public function recalculateStatus(): self
    {
        if (empty($this->reservations)) {
            $this->status = new GroupReservationStatus(GroupReservationStatus::STATUS_ACTIVE);
            return $this;
        }

        $statuses = $this->getReservationStatuses();
        $uniqueStatuses = array_unique($statuses);
        $statusCount = count($uniqueStatuses);

        // Determine new status based on reservation states
        $newStatus = $this->determineGroupStatus($statuses, $uniqueStatuses, $statusCount);
        $this->status = new GroupReservationStatus($newStatus);

        return $this;
    }

    /**
     * Check if group has any overdue reservations.
     *
     * @return bool
     */
    public function hasOverdueReservations(): bool
    {
        return in_array(
            ReservationStatus::STATUS_OVERDUE,
            $this->getReservationStatuses(),
            true
        );
    }

    /**
     * Check if all reservations are confirmed.
     *
     * @return bool
     */
    public function isFullyConfirmed(): bool
    {
        if (empty($this->reservations)) {
            return false;
        }

        $statuses = $this->getReservationStatuses();
        return count(array_unique($statuses)) === 1
            && $statuses[0] === ReservationStatus::STATUS_CONFIRMED;
    }

    /**
     * Check if all reservations are cancelled.
     *
     * @return bool
     */
    public function isFullyCancelled(): bool
    {
        if (empty($this->reservations)) {
            return false;
        }

        $statuses = $this->getReservationStatuses();
        return count(array_unique($statuses)) === 1
            && $statuses[0] === ReservationStatus::STATUS_CANCELLED;
    }

    // ========================================================================
    // Reservation Management
    // ========================================================================

    /**
     * Get all reservations in the group.
     *
     * @return Reservation[]
     */
    public function getReservations(): array
    {
        return $this->reservations;
    }

    /**
     * Get number of reservations in the group.
     *
     * @return int
     */
    public function getReservationCount(): int
    {
        return count($this->reservations);
    }

    /**
     * Add a reservation to the group.
     * Updates group status after adding.
     *
     * @param Reservation $reservation Reservation to add
     * @return $this
     */
    public function addReservation(Reservation $reservation): self
    {
        // Prevent duplicate additions
        if (in_array($reservation, $this->reservations, true)) {
            return $this;
        }

        $this->reservations[] = $reservation;
        $reservation->setGroup($this);
        $this->recalculateStatus();

        return $this;
    }

    /**
     * Remove a reservation from the group.
     * Updates group status after removal.
     *
     * @param Reservation $reservation Reservation to remove
     * @return $this
     */
    public function removeReservation(Reservation $reservation): self
    {
        foreach ($this->reservations as $key => $existingReservation) {
            if ($existingReservation === $reservation) {
                unset($this->reservations[$key]);
                $this->reservations = array_values($this->reservations); // Re-index array
                $reservation->setGroup(null);
                $this->recalculateStatus();
                break;
            }
        }

        return $this;
    }

    /**
     * Remove all reservations from the group.
     *
     * @return $this
     */
    public function clearReservations(): self
    {
        foreach ($this->reservations as $reservation) {
            $reservation->setGroup(null);
        }

        $this->reservations = [];
        $this->recalculateStatus();

        return $this;
    }

    /**
     * Find reservation by ID.
     *
     * @param int $id Reservation ID
     * @return Reservation|null
     */
    public function findReservation(int $id): ?Reservation
    {
        foreach ($this->reservations as $reservation) {
            if ($reservation->getId() === $id) {
                return $reservation;
            }
        }

        return null;
    }

    // ========================================================================
    // Group Analysis Methods
    // ========================================================================

    /**
     * Get reservations by status.
     *
     * @param string $status Status to filter by
     * @return Reservation[]
     */
    public function getReservationsByStatus(string $status): array
    {
        return array_filter(
            $this->reservations,
            fn(Reservation $r) => (string) $r->getStatus() === $status
        );
    }

    /**
     * Get total amount for all reservations in the group.
     *
     * @return float Total amount in base currency units
     */
    public function getTotalGroupAmount(): float
    {
        $total = 0;

        foreach ($this->reservations as $reservation) {
            $total += $reservation->getTotalAmount()->getAmount()->toFloat();
        }

        return $total;
    }

    /**
     * Get earliest check-in date from all reservations.
     *
     * @return \DateTimeImmutable|null
     */
    public function getEarliestCheckIn(): ?\DateTimeImmutable
    {
        if (empty($this->reservations)) {
            return null;
        }

        $dates = array_map(
            fn(Reservation $r) => $r->getMinPossibleCheckInDateTime(),
            $this->reservations
        );

        $dates = array_filter($dates); // Remove nulls

        return !empty($dates) ? min($dates) : null;
    }

    /**
     * Get latest check-out date from all reservations.
     *
     * @return \DateTimeImmutable|null
     */
    public function getLatestCheckOut(): ?\DateTimeImmutable
    {
        if (empty($this->reservations)) {
            return null;
        }

        $dates = array_map(
            fn(Reservation $r) => $r->getMaxPossibleCheckOutDateTime(),
            $this->reservations
        );

        $dates = array_filter($dates); // Remove nulls

        return !empty($dates) ? max($dates) : null;
    }

    /**
     * Get total number of guests across all reservations.
     *
     * @return array ['adults' => int, 'children' => int]
     */
    public function getTotalGuestCount(): array
    {
        $adults = 0;
        $children = 0;

        foreach ($this->reservations as $reservation) {
            foreach ($reservation->getAccommodationItems() as $item) {
                $adults += $item->getAdults();
                $children += count($item->getChildren());
            }
        }

        return [
            'adults' => $adults,
            'children' => $children,
            'total' => $adults + $children
        ];
    }

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

    /**
     * Get array of reservation status strings.
     *
     * @return string[]
     */
    private function getReservationStatuses(): array
    {
        return array_map(
            fn(Reservation $r) => (string) $r->getStatus(),
            $this->reservations
        );
    }

    /**
     * Determine group status based on reservation statuses.
     *
     * @param array $statuses All reservation statuses
     * @param array $uniqueStatuses Unique status values
     * @param int $statusCount Count of unique statuses
     * @return string New group status or null if unchanged
     */
    private function determineGroupStatus(
        array $statuses,
        array $uniqueStatuses,
        int $statusCount
    ): string {
        // All same status scenarios
        if ($statusCount === 1) {
            switch ($uniqueStatuses[0]) {
                case ReservationStatus::STATUS_OVERDUE:
                    return GroupReservationStatus::STATUS_OVERDUE;
                case ReservationStatus::STATUS_CANCELLED:
                    return GroupReservationStatus::STATUS_CANCELLED;
                case ReservationStatus::STATUS_CONFIRMED:
                    return GroupReservationStatus::STATUS_CONFIRMED;
            }
        }

        // Mixed status scenarios
        if (in_array(ReservationStatus::STATUS_OVERDUE, $statuses, true)) {
            return GroupReservationStatus::STATUS_PARTIALLY_OVERDUE;
        }

        if (in_array(ReservationStatus::STATUS_CONFIRMED, $statuses, true)) {
            return GroupReservationStatus::STATUS_PARTIALLY_CONFIRMED;
        }

        return GroupReservationStatus::STATUS_ACTIVE;
    }
}
