<?php

declare(strict_types=1);

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

namespace App\Support\AuditLogDisplay;

use App\Models\DberpAdminPermission;
use App\Models\DberpAdminRole;
use App\Models\DberpAuditLog;
use App\Models\DberpCustomer;
use App\Models\DberpCustomerCategory;
use App\Models\DberpNumberSequence;
use App\Models\DberpProduct;
use App\Models\DberpProductBrand;
use App\Models\DberpProductCategory;
use App\Models\DberpPurchaseOrder;
use App\Models\DberpSalesOrder;
use App\Models\DberpSupplier;
use App\Models\DberpSupplierCategory;
use App\Models\DberpUnit;
use App\Models\DberpWarehouse;
use App\Models\User;
use App\Support\DberpAuditDisplay;
use Illuminate\Database\Eloquent\Model;
use Throwable;

/**
 * 从审计主体与快照中推断「业务对象展示名称」，用于摘要与删除类描述。
 */
final class DberpAuditLogSubjectResolver
{
    /**
     * @var list<string>
     */
    private const SUBJECT_PRIMARY_KEYS = [
        'name',
        'display_name',
        'customer_name',
        'supplier_name',
        'product_name',
        'warehouse_name',
        'unit_name',
        'category_name',
        'title',
    ];

    /**
     * @var list<string>
     */
    private const SUBJECT_IDENTIFIER_KEYS = [
        'code',
        'document_no',
        'brand_code',
    ];

    /**
     * @var array<string, array{class-string<Model>, list<string>}>
     */
    private const AUDITABLE_FETCH = [
        'DberpCustomerCategory' => [DberpCustomerCategory::class, ['name', 'code']],
        'DberpSupplierCategory' => [DberpSupplierCategory::class, ['name', 'code']],
        'DberpCustomer' => [DberpCustomer::class, ['name', 'code']],
        'DberpSupplier' => [DberpSupplier::class, ['name', 'code']],
        'DberpWarehouse' => [DberpWarehouse::class, ['name', 'code']],
        'DberpProduct' => [DberpProduct::class, ['name', 'code']],
        'DberpUnit' => [DberpUnit::class, ['name', 'code']],
        'DberpProductCategory' => [DberpProductCategory::class, ['name', 'code']],
        'DberpProductBrand' => [DberpProductBrand::class, ['name', 'brand_code', 'short_name']],
        'DberpNumberSequence' => [DberpNumberSequence::class, ['name', 'code']],
        'DberpPurchaseOrder' => [DberpPurchaseOrder::class, ['code']],
        'DberpSalesOrder' => [DberpSalesOrder::class, ['code']],
        'DberpAdminRole' => [DberpAdminRole::class, ['display_name', 'name']],
        'DberpAdminPermission' => [DberpAdminPermission::class, ['display_name', 'name']],
        'User' => [User::class, ['name', 'login_account']],
    ];

    public function resolveDisplayTitle(DberpAuditLog $log, DberpAuditLogRelationLabelResolver $relations): string
    {
        $title = $this->resolveFromSnapshots($log);
        if ($title !== null && $title !== '') {
            return $title;
        }

        $short = $this->auditableShort($log->auditable_type);
        $id = $this->normalizeAuditableId($log->auditable_id);
        if ($short === null || $id === null) {
            return (string) __('dberp.audit_display.subject_unknown');
        }

        if (! isset(self::AUDITABLE_FETCH[$short])) {
            return (string) __('dberp.audit_display.subject_fallback_with_id', [
                'entity' => DberpAuditDisplay::auditableTypeLabel($log->auditable_type),
                'id' => $id,
            ]);
        }

        [$modelClass, $columns] = self::AUDITABLE_FETCH[$short];
        $relations->preloadAuditable($modelClass, $id);

        try {
            /** @var Model|null $row */
            $row = $modelClass::query()->whereKey($id)->first($columns);
        } catch (Throwable) {
            return (string) __('dberp.audit_display.deleted_relation_record', ['id' => $id]);
        }

        if ($row === null) {
            return (string) __('dberp.audit_display.deleted_relation_record', ['id' => $id]);
        }

        foreach ($columns as $column) {
            $value = $row->getAttribute($column);
            if (is_string($value) && $value !== '') {
                return $value;
            }
        }

        return (string) __('dberp.audit_display.deleted_relation_record', ['id' => $id]);
    }

    private function resolveFromSnapshots(DberpAuditLog $log): ?string
    {
        foreach ([$log->new_values, $log->old_values] as $values) {
            if (! is_array($values)) {
                continue;
            }

            foreach (self::SUBJECT_PRIMARY_KEYS as $key) {
                $candidate = $values[$key] ?? null;
                if (is_string($candidate) && $candidate !== '') {
                    return $candidate;
                }
            }
        }

        foreach ([$log->new_values, $log->old_values] as $values) {
            if (! is_array($values)) {
                continue;
            }

            foreach (self::SUBJECT_IDENTIFIER_KEYS as $key) {
                $candidate = $values[$key] ?? null;
                if (! is_string($candidate) || $candidate === '') {
                    continue;
                }

                return (string) __('dberp.audit_display.subject_with_identifier', [
                    'label' => (string) __('dberp.audit_display.subject_identifier_label'),
                    'value' => $candidate,
                ]);
            }
        }

        return null;
    }

    private function auditableShort(?string $auditableType): ?string
    {
        if ($auditableType === null || $auditableType === '') {
            return null;
        }

        return class_basename($auditableType);
    }

    private function normalizeAuditableId(mixed $auditableId): ?int
    {
        if ($auditableId === null || $auditableId === '') {
            return null;
        }

        if (is_int($auditableId)) {
            return $auditableId > 0 ? $auditableId : null;
        }

        if (is_string($auditableId) && ctype_digit($auditableId)) {
            $id = (int) $auditableId;

            return $id > 0 ? $id : null;
        }

        return null;
    }
}
