<?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\DateUnit\Calculator\Strategy;

use BookiFlex\Core\Model\AccommodationType\AccommodationPlacementTypeInterface;
use BookiFlex\Core\Model\AccommodationType\Scheme\AllocationScheme;
use BookiFlex\Core\Model\DateUnit\{DateUnit, Pricing\DateUnitPrice};
use BookiFlex\Core\Model\DateUnit\Calculator\{CalculatedPriceResult, PriceCalculatorInterface};
use BookiFlex\Core\Model\DateUnit\Exception\PriceNotSetByDateException;
use BookiFlex\Core\ValueObject\MoneyAmount;

defined( 'ABSPATH' ) || exit;

/**
 * Standard price calculation strategy
 *
 * Rules:
 * - Adult price on main places NOT multiplied by quantity (total room price)
 * - Price on extra places multiplied by number of places
 * - Children price multiplied by number of children
 */
class StandardPriceCalculator implements PriceCalculatorInterface
{
    public function getName(): string
    {
        return 'standard';
    }

    public function calculate(array $dateUnits, AllocationScheme $allocationScheme): CalculatedPriceResult
    {
        $dailyPrices = [];

        foreach ($dateUnits as $dateUnit) {
            $dailyPrices[] = $this->calculateDailyPrice($dateUnit, $allocationScheme);
        }

        return new CalculatedPriceResult($dailyPrices);
    }

    /**
     * Calculate price for one day
     * @throws PriceNotSetByDateException
     */
    private function calculateDailyPrice(DateUnit $dateUnit, AllocationScheme $allocationScheme): array
    {
        $price = $this->getPrice($dateUnit);
        $allocations = $allocationScheme->toArray();

        $totalPrice = MoneyAmount::zero();
        $originalTotalPrice = null;
        $details = [];

        foreach ($allocations as $placementType => $variants) {
            foreach ($variants as $variant => $quantity) {
                // Get prices
                $unitPrice = $this->getPriceAmount($price, $placementType, $variant, $dateUnit->getDate());
                $originalPrice = $this->getOriginalPriceAmount($price, $placementType, $variant);

                // Calculate total cost
                $total = $this->calculateTotal($placementType, $unitPrice, $quantity);
                $originalTotal = $originalPrice ? $this->calculateTotal($placementType, $originalPrice, $quantity) : null;

                // Add to total amount
                $totalPrice = $totalPrice->add($total);
                if ($originalTotal !== null) {
                    $originalTotalPrice = $originalTotalPrice ?? MoneyAmount::zero();
                    $originalTotalPrice = $originalTotalPrice->add($originalTotal);
                }

                // Save details
                $details[] = [
                    'placementType' => $placementType,
                    'variant' => $variant,
                    'quantity' => $quantity,
                    'price' => $unitPrice,
                    'total' => $total,
                    'originalPrice' => $originalPrice,
                    'originalTotal' => $originalTotal,
                ];
            }
        }

        return [
            'date' => $dateUnit->getDate(),
            'totalPrice' => $totalPrice,
            'originalTotalPrice' => $originalTotalPrice,
            'details' => $details,
        ];
    }

    /**
     * Get price from DateUnit
     */
    private function getPrice(DateUnit $dateUnit): DateUnitPrice
    {
        $price = $dateUnit->getPrice();

        if ($price === null) {
            throw new PriceNotSetByDateException(
                sprintf(
                    'Price is not set on date %s.',
                    $dateUnit->getDate()
                ),
                400,
                null,
                ['date' => $dateUnit->getDate()]
            );
        }

        return $price;
    }

    /**
     * Get amount for type and variant
     */
    private function getPriceAmount(DateUnitPrice $price, string $placementType, string $variant, string $date): MoneyAmount
    {
        $amount = $price->getAmount($placementType, $variant);

        if ($amount === null) {
            throw new PriceNotSetByDateException(
                sprintf(
                    'Price not set for date %1$s, placement %2$s, variant %3$s',
                    $date,
                    $placementType,
                    $variant
                ),
                400,
                null,
                [
                    'date' => $date,
                    'placement' => $placementType,
                    'variant' => $variant
                ]
            );
        }

        return $amount;
    }

    /**
     * Get original price (before modifiers)
     */
    private function getOriginalPriceAmount(DateUnitPrice $price, string $placementType, string $variant): ?MoneyAmount
    {
        $parent = $price->getParent();
        return $parent?->getAmount($placementType, $variant);
    }

    /**
     * Calculate total cost considering quantity
     */
    protected function calculateTotal(string $placementType, MoneyAmount $unitPrice, int $quantity): MoneyAmount
    {
        // Main rule: price for adults on main places - this is price for entire room
        if ($placementType === AccommodationPlacementTypeInterface::ADULT_ON_MAIN_PLACE) {
            return $unitPrice;
        }

        // For all others - multiply by quantity
        return $unitPrice->multiply($quantity);
    }
}
