<?php
declare(strict_types=1);
namespace Aen\MoneyWorkshop;

use Brick\Math\RoundingMode;
use Brick\Money\Money;
use InvalidArgumentException;

final class Cart
{
    /** Validate canonical decimal strings before handing them to the library. */
    public static function decimal(mixed $value, int $maxFraction = 6): string
    {
        if (!is_string($value) || strlen($value) > 40 || !preg_match('/^-?(?:0|[1-9][0-9]{0,29})(?:\.[0-9]{1,' . $maxFraction . '})?$/D', $value)) {
            throw new InvalidArgumentException('Amount must be a canonical decimal string; no exponent, spaces or comma.');
        }
        return $value;
    }

    public static function calculate(array $input): array
    {
        $currency = $input['currency'] ?? null;
        if (!in_array($currency, ['RUB', 'USD', 'EUR'], true)) {
            throw new InvalidArgumentException('This cart accepts RUB, USD or EUR.');
        }
        $rows = $input['items'] ?? null;
        if (!is_array($rows) || !array_is_list($rows) || count($rows) < 1 || count($rows) > 50) {
            throw new InvalidArgumentException('Provide 1 to 50 cart items.');
        }
        $discountBps = $input['discountBps'] ?? 0;
        if (!is_int($discountBps) || $discountBps < 0 || $discountBps > 10000) {
            throw new InvalidArgumentException('discountBps must be an integer from 0 to 10000.');
        }
        $subtotal = Money::zero($currency);
        $items = [];
        foreach ($rows as $row) {
            if (!is_array($row)) { throw new InvalidArgumentException('Each item must be an object.'); }
            $qty = $row['quantity'] ?? null;
            $sku = $row['sku'] ?? null;
            if (!is_int($qty) || $qty < 1 || $qty > 1000 || !is_string($sku) || !preg_match('/^[A-Za-z0-9_-]{1,40}$/D', $sku)) {
                throw new InvalidArgumentException('Use an ASCII SKU and an integer quantity from 1 to 1000.');
            }
            $unit = Money::of(self::decimal($row['unitPrice'] ?? null), $currency);
            if ($unit->isNegative() || $unit->isGreaterThan('1000000')) {
                throw new InvalidArgumentException('Unit price must be between 0 and 1000000.');
            }
            $line = $unit->multipliedBy($qty);
            $subtotal = $subtotal->plus($line);
            $items[] = ['sku' => $sku, 'quantity' => $qty, 'unitPrice' => (string) $unit->getAmount(), 'lineTotal' => (string) $line->getAmount()];
        }
        $delivery = Money::of(self::decimal($input['delivery'] ?? '0.00'), $currency);
        if ($delivery->isNegative() || $delivery->isGreaterThan('1000000')) {
            throw new InvalidArgumentException('Delivery must be between 0 and 1000000.');
        }
        // One documented policy: round the discount on the subtotal once, before delivery.
        $discount = $subtotal->multipliedBy($discountBps)->dividedBy(10000, RoundingMode::HalfUp);
        $total = $subtotal->minus($discount)->plus($delivery);
        return ['currency' => $currency, 'items' => $items, 'subtotal' => (string) $subtotal->getAmount(), 'discountBps' => $discountBps,
            'discount' => (string) $discount->getAmount(), 'delivery' => (string) $delivery->getAmount(),
            'total' => (string) $total->getAmount(), 'amountMinor' => (string) $total->getMinorAmount(),
            'roundingPolicy' => 'discount on subtotal, HalfUp, before delivery'];
    }
}
