<?php

namespace App\Services;

use InvalidArgumentException;

class VatCalculationService
{
    public function line(
        string|int|float $quantity,
        string|int|float $unitPrice,
        string|int|float $vatRate,
        string $priceMode = 'Exclusive VAT',
        string $discountType = 'fixed',
        string|int|float $discountValue = '0'
    ): array {
        $qty = $this->number($quantity, 4);
        $price = $this->number($unitPrice, 4);
        $rate = $this->number($vatRate, 4);
        $gross = bcmul($qty, $price, 4);

        $discount = match ($discountType) {
            'percentage' => bcdiv(bcmul($gross, $this->number($discountValue, 4), 6), '100', 4),
            'fixed' => $this->number($discountValue, 4),
            default => throw new InvalidArgumentException('Discount type must be fixed or percentage.'),
        };

        if (bccomp($discount, $gross, 4) === 1) {
            throw new InvalidArgumentException('Discount cannot exceed the line gross amount.');
        }

        $afterDiscount = bcsub($gross, $discount, 4);
        if ($priceMode === 'Inclusive VAT') {
            $divisor = bcadd('1', bcdiv($rate, '100', 8), 8);
            $taxable = bcdiv($afterDiscount, $divisor, 4);
            $vat = bcsub($afterDiscount, $taxable, 4);
            $total = $afterDiscount;
        } else {
            $taxable = $afterDiscount;
            $vat = bcdiv(bcmul($taxable, $rate, 6), '100', 4);
            $total = bcadd($taxable, $vat, 4);
        }

        return [
            'gross' => $this->money($gross),
            'discount' => $this->money($discount),
            'taxable' => $this->money($taxable),
            'vat' => $this->money($vat),
            'total' => $this->money($total),
        ];
    }

    public function add(string|int|float $left, string|int|float $right, int $scale = 2): string
    {
        return bcadd((string) $left, (string) $right, $scale);
    }

    public function subtract(string|int|float $left, string|int|float $right, int $scale = 2): string
    {
        return bcsub((string) $left, (string) $right, $scale);
    }

    public function multiply(string|int|float $left, string|int|float $right, int $scale = 4): string
    {
        return bcmul((string) $left, (string) $right, $scale);
    }

    public function divide(string|int|float $left, string|int|float $right, int $scale = 4): string
    {
        if (bccomp((string) $right, '0', $scale) === 0) {
            throw new InvalidArgumentException('Division by zero is not allowed.');
        }
        return bcdiv((string) $left, (string) $right, $scale);
    }

    public function money(string|int|float $value): string
    {
        return number_format(round((float) $value + PHP_FLOAT_EPSILON, 2), 2, '.', '');
    }

    public function number(string|int|float $value, int $scale = 4): string
    {
        if (!is_numeric($value)) {
            throw new InvalidArgumentException('A numeric value is required.');
        }
        return bcadd((string) $value, '0', $scale);
    }
}
