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

use BookiFlex\Core\Contract\Payment\PaymentAllocationInterface;
use BookiFlex\Core\Contract\Payment\PaymentAllocationProviderInterface;
use BookiFlex\Core\Contract\Payment\PaymentStatus;
use BookiFlex\Core\Model\Reservation\Reservation;
use BookiFlex\Core\Repository\ReservationQueryFilter;
use BookiFlex\Core\Repository\ReservationRepository;
use Brick\Money\Money;

defined( 'ABSPATH' ) || exit;

/**
 * Service for retrieving and enriching reservation data for admin list display.
 *
 * This service orchestrates:
 * - Fetching reservations from repository with filters and pagination
 * - Enriching reservation data with payment allocations
 * - Calculating prepayment and paid amounts
 * - Managing deletion with audit trail
 *
 * @package BookiFlex\Core\Service\Reservation
 */
class ReservationListService
{
    public function __construct(
        private readonly ReservationRepository $reservationRepository,
        private readonly ?PaymentAllocationProviderInterface $paymentAllocationProvider = null,
    ) {
    }

    /**
     * Get reservations for table list with prepayment enrichment.
     *
     * Returns structure with:
     * - reservations: Reservation[] - Domain model entities
     * - prepayments: array - Prepayment data indexed by reservation ID
     *
     * @param ReservationQueryFilter $filter Query filters and pagination
     * @return array{reservations: Reservation[], prepayments: array<int, array{prepayment: Money, paid: Money}>}
     */
    public function getReservations(ReservationQueryFilter $filter): array
    {
        // Get domain models from repository
        $reservations = $this->reservationRepository->findPaginated($filter);

        if (empty($reservations)) {
            return [
                'reservations' => [],
                'prepayments' => [],
            ];
        }

        // Get payment allocations for these reservations
        $paymentAllocations = [];
        if ($this->paymentAllocationProvider !== null) {
            $paymentAllocations = $this->paymentAllocationProvider->getPaymentAllocations($reservations);
        }

        // Calculate prepayment data per reservation
        $prepaymentData = $this->calculatePrepayments($reservations, $paymentAllocations);

        // Return enriched structure
        return [
            'reservations' => $reservations,
            'prepayments' => $prepaymentData,
        ];
    }

    /**
     * Count total reservations matching filters.
     *
     * @param ReservationQueryFilter $filter Query filters (pagination ignored)
     * @return int Total count
     */
    public function countReservations(ReservationQueryFilter $filter): int
    {
        return $this->reservationRepository->countFiltered($filter);
    }

    /**
     * Get status distribution for filter tabs.
     *
     * Returns count of reservations grouped by status.
     * Typically called with filter that has no status constraint.
     *
     * @param ReservationQueryFilter $filter Query filters (status typically null)
     * @return array<string, int> Map of status => count
     */
    public function getStatusCounts(ReservationQueryFilter $filter): array
    {
        return $this->reservationRepository->getStatusCounts($filter);
    }

    /**
     * Delete reservations and return audit data.
     *
     * Returns:
     * - deleted: int - Number of deleted reservations
     * - reservations: Reservation[] - Deleted reservation entities (for logging)
     *
     * @param int[] $reservationIds Array of reservation IDs to delete
     * @return array{deleted: int, reservations: Reservation[]}
     */
    public function deleteReservations(array $reservationIds): array
    {
        if (empty($reservationIds)) {
            return [
                'deleted' => 0,
                'reservations' => [],
            ];
        }

        // Get reservation details before deletion (for audit trail)
        $reservations = $this->reservationRepository->findByIdsWithRelations($reservationIds);

        // Delete reservations
        $deletedCount = $this->reservationRepository->deleteByIds($reservationIds);

        return [
            'deleted' => $deletedCount,
            'reservations' => $reservations,
        ];
    }

    /**
     * Calculate prepayment and paid amounts per reservation.
     *
     * Aggregates payment allocations by reservation, calculating:
     * - prepayment: Total amount allocated to reservation (excluding cancelled payments)
     * - paid: Total amount actually paid (only PAID status payments)
     *
     * @param Reservation[] $reservations Array of reservations
     * @param PaymentAllocationInterface[] $paymentAllocations Array of payment allocations
     * @return array<int, array{prepayment: Money, paid: Money}> Map of reservation ID => amounts
     */
    private function calculatePrepayments(array $reservations, array $paymentAllocations): array
    {
        $result = [];

        // Initialize with zero for all reservations
        foreach ($reservations as $reservation) {
            $result[$reservation->getId()] = [
                'prepayment' => Money::zero($reservation->getCurrency()),
                'paid' => Money::zero($reservation->getCurrency()),
            ];
        }

        // Aggregate payment allocations
        foreach ($paymentAllocations as $allocation) {
            $reservationId = $allocation->getReservation()->getId();
            $payment = $allocation->getPayment();

            // Skip cancelled payments
            if ($payment->getStatus()->isEquals(PaymentStatus::CANCELLED)) {
                continue;
            }

            $amount = $allocation->getAmount();

            // Add to prepayment (amount allocated to this reservation)
            $result[$reservationId]['prepayment'] = $result[$reservationId]['prepayment']->plus($amount);

            // Add to paid if payment is completed
            if ($payment->getStatus()->isEquals(PaymentStatus::PAID)) {
                $result[$reservationId]['paid'] = $result[$reservationId]['paid']->plus($amount);
            }
        }

        return $result;
    }
}
