<?php

declare(strict_types=1);

/**
 * 本文件属于 DBErp 进销存系统。
 *
 * 版权所有 © 2026 北京珑大钜商科技有限公司。保留所有权利。
 *
 * 未经官方授权，不得删除、篡改或隐藏本文件中的版权声明、产品标识与来源信息。
 * 商业授权、官方更新与技术支持服务，请以珑大官方支持中心记录为准。
 *
 * @link https://support.loongdom.cn
 * @see  https://www.dberp.com.cn
 */

namespace App\Business\Inventory;

use App\Business\Report\QueryCenterService;
use App\Models\DberpInventoryTransaction;
use App\Support\DberpNumberFormatter;
use App\Tools\Entity\EntityReference;
use App\Tools\Entity\EntityType;
use App\Tools\Support\ToolDateRange;
use App\Tools\Support\ToolPagedQuery;

/**
 * 库存流水只读查询。复用 QueryCenterService 过滤口径，限制日期跨度，不暴露备注与幂等键。
 */
final class InventoryTransactionReadQuery
{
    public const SORTABLE = ['id', 'business_time', 'product_id', 'warehouse_id', 'change_quantity'];

    public const DEFAULT_SORT = 'business_time';

    public const MAX_DATE_SPAN_DAYS = ToolDateRange::DEFAULT_MAX_SPAN_DAYS;

    public function __construct(
        private readonly QueryCenterService $queryCenterService,
    ) {}

    /**
     * @param  array<string, mixed>  $input
     * @return array{items: list<array<string, mixed>>, page: int, per_page: int, total: int}
     */
    public function search(array $input): array
    {
        $this->assertDateWindow($input);

        $query = $this->queryCenterService->inventoryTransactionQuery($this->filtersFromInput($input));
        $page = ToolPagedQuery::paginate(
            $query,
            $input,
            self::SORTABLE,
            self::DEFAULT_SORT,
        );

        return [
            'items' => $page['rows']
                ->map(fn (mixed $row): array => $this->mapTransaction($row))
                ->values()
                ->all(),
            ...ToolPagedQuery::meta($page),
        ];
    }

    /**
     * @param  array<string, mixed>  $input
     */
    private function assertDateWindow(array $input): void
    {
        ToolDateRange::parseOptional(
            $input['date_from'] ?? null,
            $input['date_until'] ?? null,
            'date_from',
            'date_until',
            self::MAX_DATE_SPAN_DAYS,
        );
    }

    /**
     * @param  array<string, mixed>  $input
     * @return array<string, mixed>
     */
    private function filtersFromInput(array $input): array
    {
        $filters = [];

        foreach (['product_id', 'product_sku_id', 'warehouse_id'] as $key) {
            if (($input[$key] ?? 0) > 0) {
                $filters[$key] = (int) $input[$key];
            }
        }

        foreach (['business_type', 'direction'] as $key) {
            $value = trim((string) ($input[$key] ?? ''));
            if ($value !== '') {
                $filters[$key] = $value;
            }
        }

        $from = trim((string) ($input['date_from'] ?? ''));
        if ($from !== '') {
            $filters['business_time_from'] = $from;
        }

        $until = trim((string) ($input['date_until'] ?? ''));
        if ($until !== '') {
            $filters['business_time_until'] = $until;
        }

        return $filters;
    }

    /**
     * @return array<string, mixed>
     */
    private function mapTransaction(mixed $row): array
    {
        if (! $row instanceof DberpInventoryTransaction) {
            return [];
        }

        $payload = [
            'id' => (int) $row->getKey(),
            'product_id' => (int) $row->product_id,
            'product_code' => (string) $row->product_code,
            'product_name' => (string) $row->product_name,
            'product_sku_id' => $row->product_sku_id === null ? null : (int) $row->product_sku_id,
            'sku_code' => $this->nullableString($row->sku_code),
            'spec_text' => $this->nullableString($row->spec_text),
            'warehouse_id' => (int) $row->warehouse_id,
            'warehouse_name' => (string) ($row->warehouse?->name ?? ''),
            'direction' => (string) $row->direction,
            'business_type' => (string) $row->business_type,
            'business_number' => $this->nullableString($row->business_number),
            'change_quantity' => DberpNumberFormatter::quantity($row->change_quantity),
            'before_quantity' => DberpNumberFormatter::quantity($row->before_quantity),
            'after_quantity' => DberpNumberFormatter::quantity($row->after_quantity),
            'business_time' => $row->business_time?->toIso8601String(),
        ];

        $refs = array_values(array_filter([
            EntityReference::tryMake(EntityType::Product, $row->product_id, $row->product_code, $row->product_name),
            EntityReference::tryMake(EntityType::Warehouse, $row->warehouse_id, null, $row->warehouse?->name),
        ]));

        return EntityReference::attachMany($payload, ...$refs);
    }

    private function nullableString(mixed $value): ?string
    {
        if ($value === null) {
            return null;
        }

        $normalized = trim((string) $value);

        return $normalized === '' ? null : $normalized;
    }
}
