<?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\Api\Transformer;

use BookiFlex\Core\Contract\Payment\PaymentAllocationInterface;
use BookiFlex\Core\Contract\Payment\PaymentInterface;
use BookiFlex\Core\Model\Penalty\Penalty;
use BookiFlex\Core\Model\Reservation\Reservation;
use BookiFlex\Core\Model\Reservation\ReservationAccommodationItem;
use BookiFlex\Core\Model\Reservation\ReservationExtraItem;
use BookiFlex\Core\Model\Reservation\ReservationStatus;
use BookiFlex\Core\Service\Aggregator\SummaryAggregator;
use BookiFlex\Core\Service\Aggregator\ValueObject\AggregatedSummary;
use Brick\Math\Exception\MathException;
use Brick\Money\Exception\MoneyMismatchException;
use Brick\Money\Exception\UnknownCurrencyException;
use Brick\Money\Money;

defined( 'ABSPATH' ) || exit;

class ReservationGroupTransformer
{
    private array $ratePlanNames;
    private array $accommodationTypes;
    /**
     * @var PaymentAllocationInterface[]
     */
    private array $paymentAllocations;

    /**
     * @param Reservation[] $reservations
     * @param array $ratePlanNames
     * @param array $accommodationTypes
     * @param PaymentAllocationInterface[] $paymentAllocations
     * @return array
     * @throws MathException
     */
    public function transform(
        array $reservations,
        array $ratePlanNames,
        array $accommodationTypes,
        array $paymentAllocations,
    ): array {
        $this->ratePlanNames = $ratePlanNames;
        $this->accommodationTypes = $accommodationTypes;
        $this->paymentAllocations = $paymentAllocations;

        $reservation = $reservations[0];
        $group = $reservation->getGroup();

        return [
            'isGroup' => $group !== null,
            'group' => $group ? [
                'id' => $reservation->getGroup()->getId(),
                'status' => $reservation->getGroup()->getStatus(),
                'reservations' => array_map(fn(Reservation $reservation) => $reservation->getId(), $group->getReservations()),
            ] : null,
            'reservations' => array_map(fn (Reservation $reservation) => $this->transformReservation($reservation), $reservations),
            'totals' => $this->aggregateTotals($reservations, $reservation->getCurrency())->toArray(),
        ];
    }

    /**
     * @throws UnknownCurrencyException
     * @throws MoneyMismatchException
     */
    protected function transformReservation(Reservation $reservation): array
    {
        $accommodationItem = $reservation->getAccommodationItems()[0];
        return [
            'id'                 => $reservation->getId(),
            'sid'                => $reservation->getSid(),
            'number'             => $reservation->getNumber(),
            'status'             => $reservation->getStatus()->getValue(),
            'specialRequest'     => $reservation->getSpecialRequest(),
            'totalAmount'        => $reservation->getTotalAmount()->getMinorAmount()->toInt(),
            'currency'           => $reservation->getCurrency(),

            'stay' => [
                'checkInDate'        => $reservation->getMinPossibleCheckInDateTime()->format('Y-m-d'),
                'checkOutDate'       => $reservation->getMaxPossibleCheckOutDateTime()->format('Y-m-d'),
                'los'                => count($reservation->getAccommodationItems()),
                'guests' => [
                    'adults'             => $accommodationItem->getAdults(),
                    'children'           => $accommodationItem->getChildren(),
                ],
            ],

            'customer' => [
                'firstName' => $reservation->getCustomer()->getFirstName(),
                'lastName'  => $reservation->getCustomer()->getLastName(),
                'email'     => $reservation->getCustomer()->getEmail(),
                'phone'     => $reservation->getCustomer()->getPhone(),
            ],

            'accommodation' => [
                'type' => $this->accommodationTypes[$accommodationItem->getAccommodationTypeCode()] ?? [
                        'name' => $accommodationItem->getAccommodationTypeCode(),
                        'code' => $accommodationItem->getAccommodationTypeCode()
                    ],
                'ratePlan' => [
                    'name' => $this->ratePlanNames[$accommodationItem->getRatePlanCode()] ?? $accommodationItem->getRatePlanCode(),
                    'code' => $accommodationItem->getRatePlanCode(),
                ],
                'dailyBreakdown' => array_map(
                    static fn (ReservationAccommodationItem $item) => [
                        'date'       => $item->getDate()->format('Y-m-d'),
                        'basePrice'  => $item->getBasePrice()->getMinorAmount()->toInt(),
                        'placements' => $item->getDetails(),
                    ],
                    $reservation->getAccommodationItems()
                ),
            ],

            'extras' => array_values(array_map(
                static fn (ReservationExtraItem $extra) => [
                    'name'       => $extra->getName(),
                    'code'       => $extra->getCode(),
                    'quantity'   => $extra->getQuantity(),
                    'unitPrice'  => $extra->getUnitPrice()->getMinorAmount()->toInt(),
                    'totalPrice' => $extra->getTotalPrice()->getMinorAmount()->toInt(),
                    'neutral'   => $extra->isNeutral(),
                ],
                $reservation->getExtraItems()
            )),

            'cancellation' => [
                'policy' => [
                    'name'         => $reservation->getCancellationPolicy()->getName(),
                    'consequences' => $reservation->getCancellationPolicy()->getCancellationConsequences(
                        $reservation->getMinPossibleCheckInDateTime()
                    ),
                ],
                'url'  => get_site_url(null, '/?cancel=' . base64_encode($reservation->getSid())),
                'code' => $reservation->getCancellationCode(),
                'cancelledBy' => $reservation->getCancelledBy()?->value,
                'cancelledAt' => $reservation->getCancelledAt()?->format('Y-m-d H:i:s'),
                'reason'      => $reservation->getCancellationReason(),
                'penalties' => $this->getActualPenalties($reservation),
                'estimatedPenalties' => $this->getEstimatedPenalties($reservation),
            ],

            'payment' => [
                'method' => [
                    'id' => $reservation->getPaymentType()->getId(),
                    'name' => $reservation->getPaymentType()->getName(),
                    'gateway'      => $reservation->getPaymentType()->getGatewayName(),
                    'captureToken'     => $this->getCaptureTokenUrl($reservation),
                ],
                'status' => [
                    'isWaitingPayment' => $reservation->isWaitingPayment(),
                    'waitingUntil'     => $reservation->getWaitingPaymentUntil()?->format('Y-m-d H:i:s'),
                    'waitingUntilFormatted'     => $reservation->getWaitingPaymentUntil()?->format('d M Y, H:i'),
	                'timeForConfirmation' => $reservation->getPaymentType()?->getTimeForConfirmation(),
                ],
                'amounts' => $this->calculatePayments($reservation->getTotalAmount(), $this->paymentAllocations, $reservation->getCurrency()),
                'transactions' => array_map(function (PaymentInterface $payment) {
                    return [
                        'id'          => $payment->getId(),
                        'amount'      => $payment->getAmount()->getMinorAmount()->toInt(),
                        'status'      => (string) $payment->getStatus(),
                        'gatewayName' => $payment->getGatewayName(),
                    ];
                }, $this->calculatePaymentsItems($this->paymentAllocations)),
            ],
        ];
    }

    private function aggregateTotals(array $reservations, string $currency): AggregatedSummary
    {
        $aggregator = new SummaryAggregator($currency);

        foreach ($reservations as $reservation) {
            // Add main reservation summary
            $aggregator->addSummary(
                $reservation->getCashier(),
                'reservation',
                'total',
                ['id' => $reservation->getSid()]
            );

            // Add daily accommodation summaries
            foreach ($reservation->getAccommodationItems() as $item) {
                $aggregator->addSummary(
                    $item->getCashier(),
                    'accommodation',
                    'daily',
                    [
                        'id' => $item->getReservation()->getId(),
                        'date' => $item->getDate()->format('Y-m-d'),
                        'itemId' => $item->getId(),
                        'accommodationType' => $item->getAccommodationTypeCode(),
                        'ratePlan' => $item->getRatePlanCode()
                    ]
                );
            }

            foreach ($reservation->getExtraItems() as $extra) {
                if ($extra->isNeutral()) {
                    continue;
                }

                $aggregator->addSummary(
                    $extra->getCashier(),
                    'extra',
                    'item',
                    [
                        'name'      => $extra->getName(),
                        'code'      => $extra->getCode(),
                        'quantity'  => $extra->getQuantity(),
                        'refundable' => $extra->isRefundable(),
                    ]
                );
            }
        }

        $result = $aggregator->aggregate();

        // Enrich modifiers with display names from reservation adjustments
        $nameMap = [];
        foreach ($reservations as $reservation) {
            foreach ($reservation->getAdjustments() as $adj) {
                if (!isset($nameMap[$adj->getCode()])) {
                    $nameMap[$adj->getCode()] = $adj->getName();
                }
            }
            foreach ($reservation->getExtraItems() as $extra) {
                foreach ($extra->getAdjustments() as $adj) {
                    if (!isset($nameMap[$adj->getCode()])) {
                        $nameMap[$adj->getCode()] = $adj->getName();
                    }
                }
            }
        }

        if (!empty($nameMap)) {
            $enrichedModifiers = array_map(
                static function (array $modifier) use ($nameMap): array {
                    $modifier['name'] = $nameMap[$modifier['code']] ?? null;
                    return $modifier;
                },
                $result->toArray()['modifiers']
            );
            $result->setModifiers($enrichedModifiers);
        }

        return $result;
    }

    /**
     * @param PaymentAllocationInterface[] $paymentAllocations
     * @return array
     */
    private function calculatePaymentsItems(array $paymentAllocations): array
    {
        $payments = [];
        foreach ($paymentAllocations as $paymentAllocation) {
            $payment                     = $paymentAllocation->getPayment();
            $payments[$payment->getId()] = $payment;
        }

        return array_values($payments);
    }

    /**
     * @param Money $totalAmount
     * @param PaymentAllocationInterface[] $paymentAllocations
     * @param string $currency
     * @return array
     * @throws MathException
     * @throws MoneyMismatchException
     */
    private function calculatePayments(Money $totalAmount, array $paymentAllocations, string $currency): array
    {
        $prepayments = [];
        $paid        = [];
        foreach ($paymentAllocations as $paymentAllocation) {
            if ($paymentAllocation->getPayment()->isPaid()) {
                $paid[] = $paymentAllocation->getAmount();
            }
            $prepayments[] = $paymentAllocation->getAmount();
        }

        $prepaymentAmount = Money::total(Money::zero($currency), ...$prepayments);
        $paidAmount       = Money::total(Money::zero($currency), ...$paid);

        return [
            'prepayment' => $prepaymentAmount->getMinorAmount()->toInt(),
            'paid'       => $paidAmount->getMinorAmount()->toInt(),
            'onArrival'  => $totalAmount->minus($prepaymentAmount)->getMinorAmount()->toInt(),
        ];
    }

    /**
     * Get actual penalties (only for cancelled reservations)
     *
     * @param Reservation $reservation
     * @return array
     * @throws MathException
     */
    private function getActualPenalties(Reservation $reservation): array
    {
        if (!$reservation->getStatus()->isEquals(ReservationStatus::STATUS_CANCELLED)) {
            return [];
        }

        $penalties = $reservation->getPenalties();

        return array_map(fn (Penalty $penalty) => [
            'id' => $penalty->getId(),
            'status' => $penalty->getStatus()->getValue(),
            'amount' => $penalty->getAmount()->getMinorAmount()->toInt(),
            'description' => $penalty->getDescription(),
            'comment' => $penalty->getComment(),
            'appliedAt' => $penalty->getAppliedAt()->format('Y-m-d H:i:s'),
        ], $penalties);
    }

    /**
     * Get estimated penalties if reservation is cancelled now
     *
     * @param Reservation $reservation
     * @return array
     * @throws MathException
     */
    private function getEstimatedPenalties(Reservation $reservation): array
    {
        if ($reservation->getStatus()->isEquals(ReservationStatus::STATUS_CANCELLED)) {
            return [];
        }

        $penalties = $reservation->calculatePenalties(new \DateTimeImmutable('now'));

        return array_map(fn (Penalty $penalty) => [
            'id' => $penalty->getId(),
            'status' => $penalty->getStatus()->getValue(),
            'amount' => $penalty->getAmount()->getMinorAmount()->toInt(),
            'description' => $penalty->getDescription(),
            'comment' => $penalty->getComment(),
            'appliedAt' => $penalty->getAppliedAt()->format('Y-m-d H:i:s'),
        ], $penalties);
    }

    private function getCaptureTokenUrl(Reservation $reservation): string
    {
        if (!$reservation->getPaymentType()->isInternetPayment()) {
            return '';
        }

        $url = rest_url("/bflex/v1/payment/token/public/{$reservation->getSid()}/continue");
        if (mb_strpos($url, '?') === false) {
            $url .= '?redirect=true';
        } else {
            $url .= '&redirect=true';
        }

        return $url;
    }
}
