<?php

declare(strict_types=1);

namespace App\Business\Product;

use App\Models\DberpOtherStockInOrderItem;
use App\Models\DberpOtherStockOutOrderItem;
use App\Models\DberpProduct;
use App\Models\DberpProductSku;
use App\Models\DberpPurchaseOrderItem;
use App\Models\DberpPurchaseReceiptOrderItem;
use App\Models\DberpPurchaseReturnOrderItem;
use App\Models\DberpSalesOrderItem;
use App\Models\DberpSalesOutboundOrderItem;
use App\Models\DberpSalesReturnOrderItem;
use App\Models\DberpStocktakeOrderItem;
use App\Models\DberpTransferOrderItem;
use App\Models\DberpUnit;
use App\Support\DberpNumberFormatter;
use App\Support\DberpProductAuxiliaryUnitPreview;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\ValidationException;

final class DberpDocumentLineUnitService
{
    /**
     * @deprecated 库存类明细已允许辅单位录入，由 {@see DberpProductUnitConversionService} 统一换算主单位数量。
     */
    public static function restrictsQuantityToProductBaseUnit(Model $line): bool
    {
        return false;
    }

    public static function loadProductForLine(Model $line): ?DberpProduct
    {
        $relations = ['unit', 'auxiliaryUnit'];

        if ($line->getAttribute('product_sku_id') !== null && (int) $line->getAttribute('product_sku_id') > 0) {
            $sku = DberpProductSku::query()->with(['product' => fn ($q) => $q->with($relations)])->find((int) $line->getAttribute('product_sku_id'));

            return $sku?->product;
        }

        if ($line->getAttribute('product_id') !== null && (int) $line->getAttribute('product_id') > 0) {
            return DberpProduct::query()->with($relations)->find((int) $line->getAttribute('product_id'));
        }

        return null;
    }

    /**
     * 写入/校正明细行单位、主辅快照与单位名称；在保存前调用。
     */
    public static function applySnapshotsToLine(Model $line): void
    {
        $product = self::loadProductForLine($line);
        if (! $product instanceof DberpProduct) {
            return;
        }

        $baseId = (int) $product->unit_id;
        if ($baseId <= 0) {
            throw ValidationException::withMessages([
                'unit_id' => (string) __('dberp.validation.document_line_product_missing_base_unit'),
            ]);
        }

        $auxEnabled = (bool) $product->is_auxiliary_unit_enabled;
        $auxId = $auxEnabled ? (int) ($product->auxiliary_unit_id ?? 0) : 0;
        $hasValidAux = $auxEnabled && $auxId > 0 && $auxId !== $baseId;

        $allowed = [$baseId];
        if ($hasValidAux) {
            $allowed[] = $auxId;
        }

        $selected = (int) ($line->getAttribute('unit_id') ?? 0);
        if ($selected <= 0) {
            $selected = $baseId;
        }

        if (! in_array($selected, $allowed, true)) {
            throw ValidationException::withMessages([
                'unit_id' => (string) __('dberp.validation.document_line_unit_invalid'),
            ]);
        }

        $unitRow = DberpUnit::query()->whereKey($selected)->first();
        if ($unitRow === null) {
            throw ValidationException::withMessages([
                'unit_id' => (string) __('dberp.validation.document_line_unit_invalid'),
            ]);
        }

        $isAuxiliary = $hasValidAux && $selected === $auxId;

        $line->forceFill([
            'unit_id' => $selected,
            'unit_name' => (string) $unitRow->name,
            'base_unit_id' => $baseId,
            'auxiliary_unit_id' => $hasValidAux ? $auxId : null,
            'auxiliary_unit_quantity' => $hasValidAux ? $product->auxiliary_unit_quantity : null,
            'base_unit_quantity' => $hasValidAux ? $product->base_unit_quantity : null,
            'is_auxiliary_unit' => $isAuxiliary,
        ]);

        if ($isAuxiliary) {
            self::assertAuxiliaryConversionSnapshotsPresent($line);
        }

        if ($line instanceof DberpPurchaseOrderItem || $line instanceof DberpSalesOrderItem) {
            self::applyOrderLineBaseQuantity($line);

            return;
        }

        self::applyInventoryDerivedBaseQuantities($line);
    }

    /**
     * @return array<int, string>
     */
    public static function unitOptionsForProduct(DberpProduct $product, bool $restrictToBaseOnly): array
    {
        $baseId = (int) $product->unit_id;
        if ($baseId <= 0) {
            return [];
        }

        $ids = [$baseId];
        $auxEnabled = (bool) $product->is_auxiliary_unit_enabled;
        $auxId = $auxEnabled ? (int) ($product->auxiliary_unit_id ?? 0) : 0;
        if (! $restrictToBaseOnly && $auxId > 0 && $auxId !== $baseId) {
            $ids[] = $auxId;
        }

        return DberpUnit::query()
            ->whereIn('id', $ids)
            ->orderByRaw('CASE WHEN id = ? THEN 0 ELSE 1 END', [$baseId])
            ->pluck('name', 'id')
            ->all();
    }

    public static function defaultUnitIdForProduct(DberpProduct $product): int
    {
        return (int) $product->unit_id;
    }

    public static function conversionHintForProduct(DberpProduct $product): string
    {
        return DberpProductAuxiliaryUnitPreview::fromProduct($product);
    }

    public static function approximateBaseQuantityPreview(
        mixed $enteredQuantity,
        int $selectedUnitId,
        DberpProduct $product,
    ): ?string {
        if (! (bool) $product->is_auxiliary_unit_enabled) {
            return null;
        }

        $baseId = (int) $product->unit_id;
        $auxId = (int) ($product->auxiliary_unit_id ?? 0);
        if ($selectedUnitId !== $auxId || $auxId <= 0 || $baseId <= 0) {
            return null;
        }

        $qtyString = self::normalizeQuantityString($enteredQuantity);
        if ($qtyString === null || bccomp($qtyString, '0', 6) !== 1) {
            return null;
        }

        $auxQty = self::normalizeQuantityString($product->auxiliary_unit_quantity);
        $baseQty = self::normalizeQuantityString($product->base_unit_quantity);
        if ($auxQty === null || $baseQty === null || bccomp($auxQty, '0', 6) !== 1 || bccomp($baseQty, '0', 6) !== 1) {
            return null;
        }

        $factor = bcdiv($baseQty, $auxQty, 12);
        $approx = bcmul($qtyString, $factor, 8);
        $approxTrimmed = self::trimNumericString($approx);

        $auxName = trim((string) ($product->auxiliaryUnit?->name ?? ''));
        $baseName = trim((string) ($product->unit?->name ?? ''));
        if ($auxName === '' || $baseName === '') {
            return null;
        }

        return sprintf(
            '%s %s ≈ %s %s',
            DberpNumberFormatter::quantity($qtyString),
            $auxName,
            DberpNumberFormatter::quantity($approxTrimmed),
            $baseName,
        );
    }

    private static function assertAuxiliaryConversionSnapshotsPresent(Model $line): void
    {
        $aux = $line->getAttribute('auxiliary_unit_quantity');
        $base = $line->getAttribute('base_unit_quantity');
        $auxStr = is_string($aux) ? trim($aux) : trim((string) $aux);
        $baseStr = is_string($base) ? trim($base) : trim((string) $base);

        if ($auxStr === '' || $baseStr === '' || ! is_numeric($auxStr) || ! is_numeric($baseStr)) {
            throw ValidationException::withMessages([
                'quantity' => (string) __('dberp.validation.document_line_auxiliary_conversion_missing'),
            ]);
        }

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

    private static function applyOrderLineBaseQuantity(Model $line): void
    {
        $isAux = (bool) $line->getAttribute('is_auxiliary_unit');

        $line->forceFill([
            'base_quantity' => DberpProductUnitConversionService::enteredQuantityToBaseOrZero(
                (string) ($line->getAttribute('quantity') ?? '0'),
                $isAux,
                $line->getAttribute('auxiliary_unit_quantity'),
                $line->getAttribute('base_unit_quantity'),
            ),
        ]);
    }

    private static function applyInventoryDerivedBaseQuantities(Model $line): void
    {
        $isAux = (bool) $line->getAttribute('is_auxiliary_unit');
        $auxSnap = $line->getAttribute('auxiliary_unit_quantity');
        $baseSnap = $line->getAttribute('base_unit_quantity');

        if ($line instanceof DberpPurchaseReceiptOrderItem) {
            $line->forceFill([
                'base_received_quantity' => DberpProductUnitConversionService::enteredQuantityToBaseOrZero(
                    (string) ($line->getAttribute('received_quantity') ?? '0'),
                    $isAux,
                    $auxSnap,
                    $baseSnap,
                ),
                'base_arrival_quantity' => DberpProductUnitConversionService::enteredQuantityToBaseOrZero(
                    (string) ($line->getAttribute('arrival_quantity') ?? '0'),
                    $isAux,
                    $auxSnap,
                    $baseSnap,
                ),
            ]);

            return;
        }

        if ($line instanceof DberpSalesOutboundOrderItem) {
            $line->forceFill([
                'base_ship_quantity' => DberpProductUnitConversionService::enteredQuantityToBaseOrZero(
                    (string) ($line->getAttribute('ship_quantity') ?? '0'),
                    $isAux,
                    $auxSnap,
                    $baseSnap,
                ),
            ]);

            return;
        }

        if ($line instanceof DberpPurchaseReturnOrderItem || $line instanceof DberpSalesReturnOrderItem) {
            $line->forceFill([
                'base_return_quantity' => DberpProductUnitConversionService::enteredQuantityToBaseOrZero(
                    (string) ($line->getAttribute('return_quantity') ?? '0'),
                    $isAux,
                    $auxSnap,
                    $baseSnap,
                ),
            ]);

            return;
        }

        if ($line instanceof DberpOtherStockInOrderItem
            || $line instanceof DberpOtherStockOutOrderItem
            || $line instanceof DberpTransferOrderItem) {
            $line->forceFill([
                'base_quantity' => DberpProductUnitConversionService::enteredQuantityToBaseOrZero(
                    (string) ($line->getAttribute('quantity') ?? '0'),
                    $isAux,
                    $auxSnap,
                    $baseSnap,
                ),
            ]);

            return;
        }

        if ($line instanceof DberpStocktakeOrderItem) {
            $line->forceFill([
                'base_actual_quantity' => DberpProductUnitConversionService::enteredQuantityToBaseOrZero(
                    (string) ($line->getAttribute('actual_quantity') ?? '0'),
                    $isAux,
                    $auxSnap,
                    $baseSnap,
                ),
            ]);
        }
    }

    private static function normalizeQuantityString(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 trimNumericString(string $numeric): string
    {
        if (str_contains($numeric, 'e') || str_contains($numeric, 'E')) {
            $numeric = rtrim(rtrim(sprintf('%.8F', (float) $numeric), '0'), '.');
        }

        if (! str_contains($numeric, '.')) {
            return $numeric;
        }

        return rtrim(rtrim($numeric, '0'), '.');
    }
}
