<?php

declare(strict_types=1);

namespace App\Business\Product;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\ValidationException;

/**
 * 商品计量单位换算（主单位口径），统一供采购/销售/库存单据使用。
 */
final class DberpProductUnitConversionService
{
    private const INTERNAL_SCALE = 12;

    /**
     * 将「录入数量 + 是否辅单位」换算为商品主计量单位数量（字符串，固定小数位）。
     */
    public static function enteredQuantityToBaseDecimalString(
        string $enteredQuantity,
        bool $isAuxiliaryUnitLine,
        ?string $auxiliaryUnitQuantitySnapshot,
        ?string $baseUnitQuantitySnapshot,
        int $resultScale = 4,
    ): string {
        $entered = self::normalizeBcNumeric($enteredQuantity);
        if ($entered === null) {
            throw ValidationException::withMessages([
                'quantity' => (string) __('dberp.validation.document_line_quantity_invalid'),
            ]);
        }

        if (bccomp($entered, '0', self::INTERNAL_SCALE) !== 1) {
            throw ValidationException::withMessages([
                'quantity' => (string) __('dberp.validation.document_line_quantity_invalid'),
            ]);
        }

        if (! $isAuxiliaryUnitLine) {
            return self::normalizeToScale($entered, $resultScale);
        }

        $auxQty = self::normalizeBcNumeric($auxiliaryUnitQuantitySnapshot);
        $baseQty = self::normalizeBcNumeric($baseUnitQuantitySnapshot);

        if ($auxQty === null || $baseQty === null) {
            throw ValidationException::withMessages([
                'quantity' => (string) __('dberp.validation.document_line_auxiliary_conversion_missing'),
            ]);
        }

        if (bccomp($auxQty, '0', self::INTERNAL_SCALE) !== 1 || bccomp($baseQty, '0', self::INTERNAL_SCALE) !== 1) {
            throw ValidationException::withMessages([
                'quantity' => (string) __('dberp.validation.document_line_auxiliary_conversion_invalid'),
            ]);
        }

        $ratio = bcdiv($baseQty, $auxQty, self::INTERNAL_SCALE);
        $baseAmount = bcmul($entered, $ratio, self::INTERNAL_SCALE);

        return self::normalizeToScale($baseAmount, $resultScale);
    }

    /**
     * 将录入数量换算为主单位数量；录入为 0 或无效时返回 0（用于到货量等允许为 0 的字段）。
     */
    public static function enteredQuantityToBaseOrZero(
        string $enteredQuantity,
        bool $isAuxiliaryUnitLine,
        ?string $auxiliaryUnitQuantitySnapshot,
        ?string $baseUnitQuantitySnapshot,
        int $resultScale = 4,
    ): string {
        $entered = self::normalizeBcNumeric($enteredQuantity);
        if ($entered === null || bccomp($entered, '0', self::INTERNAL_SCALE) !== 1) {
            return self::formatFixedScale('0', $resultScale);
        }

        return self::enteredQuantityToBaseDecimalString(
            $entered,
            $isAuxiliaryUnitLine,
            $auxiliaryUnitQuantitySnapshot,
            $baseUnitQuantitySnapshot,
            $resultScale,
        );
    }

    /**
     * 将已规范化的数字字符串格式化为固定小数位（用于数量字段持久化）。
     */
    public static function normalizeToScale(string $normalizedNumeric, int $scale): string
    {
        return self::formatFixedScale($normalizedNumeric, $scale);
    }

    public static function formatQuantityOrZero(string $raw, int $scale = 4): string
    {
        $trimmed = trim($raw);
        $n = self::normalizeBcNumeric($trimmed);
        if ($n === null || bccomp($n, '0', self::INTERNAL_SCALE) !== 1) {
            return self::formatFixedScale('0', $scale);
        }

        return self::formatFixedScale($n, $scale);
    }

    /**
     * 将主单位数量换算回「指定单据行单位」的录入数量（用于回写采购/销售订单执行数量等）。
     */
    public static function baseQuantityToLineEnteredString(
        string $baseQuantity,
        Model $line,
        int $resultScale = 4,
    ): string {
        $base = self::normalizeBcNumeric($baseQuantity);
        if ($base === null) {
            throw ValidationException::withMessages([
                'quantity' => (string) __('dberp.validation.document_line_quantity_invalid'),
            ]);
        }

        $lineUnitId = (int) ($line->getAttribute('unit_id') ?? 0);
        $baseUnitId = (int) ($line->getAttribute('base_unit_id') ?? 0);
        if ($lineUnitId <= 0 || $baseUnitId <= 0) {
            throw ValidationException::withMessages([
                'unit_id' => (string) __('dberp.validation.document_line_product_missing_base_unit'),
            ]);
        }

        if ($lineUnitId === $baseUnitId) {
            return self::normalizeToScale($base, $resultScale);
        }

        $isAuxiliary = (bool) $line->getAttribute('is_auxiliary_unit');
        if (! $isAuxiliary) {
            return self::normalizeToScale($base, $resultScale);
        }

        $auxQty = self::normalizeBcNumeric($line->getAttribute('auxiliary_unit_quantity'));
        $baseQty = self::normalizeBcNumeric($line->getAttribute('base_unit_quantity'));

        if ($auxQty === null || $baseQty === null) {
            throw ValidationException::withMessages([
                'quantity' => (string) __('dberp.validation.document_line_auxiliary_conversion_missing'),
            ]);
        }

        if (bccomp($auxQty, '0', self::INTERNAL_SCALE) !== 1 || bccomp($baseQty, '0', self::INTERNAL_SCALE) !== 1) {
            throw ValidationException::withMessages([
                'quantity' => (string) __('dberp.validation.document_line_auxiliary_conversion_invalid'),
            ]);
        }

        $factor = bcdiv($auxQty, $baseQty, self::INTERNAL_SCALE);
        $entered = bcmul($base, $factor, self::INTERNAL_SCALE);

        return self::normalizeToScale($entered, $resultScale);
    }

    private static function normalizeBcNumeric(mixed $value): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }

        $string = is_string($value) ? trim($value) : trim((string) $value);
        if ($string === '' || ! is_numeric($string)) {
            return null;
        }

        return $string;
    }

    private static function formatFixedScale(string $value, int $scale): string
    {
        $scaled = bcdiv($value, '1', $scale);
        if (! str_contains($scaled, '.')) {
            return $scaled.'.'.str_repeat('0', $scale);
        }

        [$whole, $frac] = explode('.', $scaled, 2);
        $frac = str_pad(substr($frac, 0, $scale), $scale, '0');

        return $whole.'.'.$frac;
    }
}
