<?php
/**
 * BookiFlex Cart Extension
 *
 * @package    BookiFlex
 * @subpackage Cart
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

namespace BookiFlex\Extensions\Cart\Service;

use BookiFlex\Extensions\Cart\Contract\CartExtraProviderInterface;
use BookiFlex\Extensions\Cart\Contract\CartItemInterface;
use BookiFlex\Extensions\Cart\Contract\CartItemProcessorInterface;
use BookiFlex\Extensions\Cart\DTO\CartExtraContext;
use BookiFlex\Extensions\Cart\Exception\InvalidItemTypeException;
use BookiFlex\Extensions\Cart\Model\ExtraCartItem;

defined( 'ABSPATH' ) || exit;

/**
 * Processor for extra-service cart items. Delegates domain knowledge (lookup,
 * pricing, validation rules) to a CartExtraProviderInterface implementation
 * supplied by the extension that owns the extras domain.
 */
class ExtraItemProcessor implements CartItemProcessorInterface
{
    private array $validationErrors = [];

    public function __construct(
        private readonly CartExtraProviderInterface $provider,
    ) {
    }

    public function createItem(array $data): CartItemInterface
    {
        return ExtraCartItem::fromRequest($data);
    }

    public function validate(CartItemInterface $item, int $quantity = 1): bool
    {
        if (!$item instanceof ExtraCartItem) {
            $this->validationErrors = ['invalid_item_type' => 'Invalid item type'];
            return false;
        }

        $this->validationErrors = [];

        if (!$item->validate()) {
            $this->validationErrors = array_merge($this->validationErrors, $item->getValidationErrors());
        }

        $snapshot = $this->provider->findExtra($item->getExtraId());
        if ($snapshot === null) {
            $this->validationErrors['extra_not_found'] = 'Extra service not found or unavailable';
            return false;
        }

        // Included extras are bundled into the rate plan by an admin decision; they do not
        // need to satisfy self-purchase rules (PER_BOOKING ban, maxPerUnit, etc.).
        if (!$item->isIncluded()) {
            $providerErrors = $this->provider->validate($snapshot, $item->getQuantity());
            if (!empty($providerErrors)) {
                $this->validationErrors = array_merge($this->validationErrors, $providerErrors);
            }
        }

        return empty($this->validationErrors);
    }

    public function getValidationErrors(): array
    {
        return $this->validationErrors;
    }

    public function process(CartItemInterface $item): CartItemInterface
    {
        if (!$item instanceof ExtraCartItem) {
            throw new InvalidItemTypeException(
                'Invalid item type',
                400,
                null,
                ['expected' => ExtraCartItem::class, 'actual' => get_class($item)]
            );
        }

        $snapshot = $this->provider->findExtra($item->getExtraId());
        if ($snapshot === null) {
            throw new InvalidItemTypeException(
                'Extra service not found or unavailable',
                404,
                null,
                ['errorCode' => 'extra_not_found', 'extraId' => $item->getExtraId()]
            );
        }

        $item->setName($snapshot->name);

        return $this->calculateItemTotal($item);
    }

    public function calculateItemTotal(CartItemInterface $item): CartItemInterface
    {
        if (!$item instanceof ExtraCartItem) {
            throw new InvalidItemTypeException(
                'Invalid item type',
                400,
                null,
                ['expected' => ExtraCartItem::class, 'actual' => get_class($item)]
            );
        }

        // Included extras are bundled into the rate plan price — always zero-cost.
        if ($item->isIncluded()) {
            $item->setUnitPrice(0);
            return $item;
        }

        $snapshot = $this->provider->findExtra($item->getExtraId());
        if ($snapshot === null) {
            $item->setUnitPrice(0);
            return $item;
        }

        $price = $this->provider->calculateUnitPrice(
            $snapshot,
            new CartExtraContext(
                guests:   $item->getGuests(),
                nights:   $item->getNights(),
                currency: $item->getCurrency(),
                quantity: $item->getQuantity(),
            )
        );

        $item->setUnitPrice($price);

        return $item;
    }

    public function checkAvailability(CartItemInterface $item, int $quantity): bool
    {
        return true;
    }
}
