<?php

declare(strict_types=1);

namespace App\Support\AuditLogDisplay;

use App\Models\DberpAuditLog;
use App\Support\DberpAuditDisplay;
use Illuminate\Support\Str;

/**
 * 审计日志展示层编排：生成操作摘要、业务变更明细与原始 JSON（不改变存储结构）。
 */
final class DberpAuditLogDisplayFormatter
{
    /**
     * @var list<string>
     */
    private const HIDDEN_SNAPSHOT_KEYS = [
        'password',
        'password_confirmation',
        'remember_token',
    ];

    /**
     * 较长后缀优先，避免 `status_changed` 被误拆为 `changed`。
     *
     * @var list<string>
     */
    private const ACTION_VERB_SUFFIX_PRIORITY = [
        'status_changed',
        'created',
        'updated',
        'deleted',
        'restored',
        'enabled',
        'disabled',
        'confirmed',
        'approved',
        'rejected',
        'voided',
        'cancelled',
        'completed',
        'imported',
        'exported',
        'submitted',
        'applied',
        'reversed',
        'closed',
        'copied',
        'activated',
        'converted',
        'synced',
        'pruned',
        'viewed',
        'filtered',
        'generated',
        'remapped',
        'invalidated',
        'blocked',
        'overridden',
        'reset',
        'effective',
        'adjusted',
        'failed',
        'cleared',
        'retried',
    ];

    public function __construct(
        private readonly DberpAuditLogFieldLabelResolver $fieldLabels,
        private readonly DberpAuditLogValueFormatter $values,
        private readonly DberpAuditLogRelationLabelResolver $relations,
        private readonly DberpAuditLogSubjectResolver $subjects,
    ) {}

    private ?int $memoLogId = null;

    private ?DberpAuditLogDisplayView $memoView = null;

    /**
     * @param  iterable<int, DberpAuditLog>  $logs
     */
    public function preloadRelationsForLogs(iterable $logs): void
    {
        $this->relations->preloadFromLogs($logs);
    }

    public function buildView(DberpAuditLog $log): DberpAuditLogDisplayView
    {
        $id = (int) $log->getKey();
        if ($this->memoLogId === $id && $this->memoView instanceof DberpAuditLogDisplayView) {
            return $this->memoView;
        }

        $this->relations->preloadFromLogs([$log]);

        $operatorLabel = filled($log->user?->name)
            ? (string) $log->user->name
            : (string) __('dberp.audit.operator_system');

        $entityLabel = DberpAuditDisplay::auditableTypeLabel($log->auditable_type);
        $subjectTitle = $this->subjects->resolveDisplayTitle($log, $this->relations);
        $structuralVerb = $this->tryResolveStructuralVerbPhrase($log->action ?? '');
        $operationSummary = $structuralVerb !== null
            ? $operatorLabel.' '.$structuralVerb.$entityLabel.'「'.$subjectTitle.'」'
            : $this->buildFallbackOperationSummary($operatorLabel, (string) ($log->action ?? ''), $subjectTitle);

        $businessLines = $this->buildBusinessDetailLines($log);

        $this->memoLogId = $id;
        $this->memoView = new DberpAuditLogDisplayView(
            operationSummary: $operationSummary,
            businessDetailLines: $businessLines,
        );

        return $this->memoView;
    }

    /**
     * 列表页轻量摘要：避免逐行 buildView() 解析字段 diff。
     */
    public function listOperationSummary(DberpAuditLog $log): string
    {
        $operatorLabel = filled($log->user?->name)
            ? (string) $log->user->name
            : (string) __('dberp.audit.operator_system');

        $action = (string) ($log->action ?? '');
        $entityLabel = DberpAuditDisplay::auditableTypeLabel($log->auditable_type);
        $subjectTitle = DberpAuditDisplay::auditableSummary($log->auditable_type, $log->auditable_id);
        $structuralVerb = $this->tryResolveStructuralVerbPhrase($action);

        if ($structuralVerb !== null) {
            return $operatorLabel.' '.$structuralVerb.$entityLabel.'「'.$subjectTitle.'」';
        }

        return $this->buildFallbackOperationSummary($operatorLabel, $action, $subjectTitle);
    }

    /**
     * @return list<string>
     */
    private function buildBusinessDetailLines(DberpAuditLog $log): array
    {
        $old = $this->filteredSnapshot($log->old_values);
        $new = $this->filteredSnapshot($log->new_values);
        $kind = $this->classifyPresentationKind($log, $old, $new);
        $auditableType = $log->auditable_type;

        if ($kind === 'delete') {
            $subject = $this->subjects->resolveDisplayTitle($log, $this->relations);
            $entity = DberpAuditDisplay::auditableTypeLabel($log->auditable_type);
            $lines = [
                (string) __('dberp.audit_display.delete_subject_line', [
                    'entity' => $entity,
                    'subject' => $subject,
                ]),
            ];

            foreach ($old as $key => $value) {
                $lines[] = $this->formatSingleFieldLine($key, $value, $auditableType);
            }

            return $lines;
        }

        if ($kind === 'create') {
            $lines = [];
            foreach ($new as $key => $value) {
                $lines[] = $this->formatSingleFieldLine($key, $value, $auditableType);
            }

            return $lines === [] ? [(string) __('dberp.audit_display.no_snapshot_detail')] : $lines;
        }

        $lines = [];
        $keys = array_values(array_unique(array_merge(array_keys($old), array_keys($new))));
        sort($keys);

        foreach ($keys as $key) {
            $before = $old[$key] ?? null;
            $after = $new[$key] ?? null;

            if ($this->valuesAreEquivalent($before, $after)) {
                continue;
            }

            $label = $this->fieldLabels->resolve($key, $auditableType);
            $beforeLabel = $this->values->format($key, $before, $auditableType, $this->relations);
            $afterLabel = $this->values->format($key, $after, $auditableType, $this->relations);

            $lines[] = (string) __('dberp.audit_display.change_from_to', [
                'label' => $label,
                'before' => $beforeLabel,
                'after' => $afterLabel,
            ]);
        }

        if ($lines === []) {
            return [(string) __('dberp.audit_display.no_field_level_changes')];
        }

        return $lines;
    }

    private function formatSingleFieldLine(string $key, mixed $value, ?string $auditableType): string
    {
        $label = $this->fieldLabels->resolve($key, $auditableType);
        $formatted = $this->values->format($key, $value, $auditableType, $this->relations);

        return $label.'：'.$formatted;
    }

    /**
     * @param  array<string, mixed>|null  $values
     * @return array<string, mixed>
     */
    private function filteredSnapshot(?array $values): array
    {
        if ($values === null || $values === []) {
            return [];
        }

        $out = [];
        foreach ($values as $key => $value) {
            if (in_array($key, self::HIDDEN_SNAPSHOT_KEYS, true)) {
                continue;
            }

            $out[$key] = $value;
        }

        return $out;
    }

    /**
     * @param  array<string, mixed>  $old
     * @param  array<string, mixed>  $new
     */
    private function classifyPresentationKind(DberpAuditLog $log, array $old, array $new): string
    {
        $action = (string) ($log->action ?? '');

        if (Str::endsWith($action, '.deleted')) {
            return 'delete';
        }

        if (Str::endsWith($action, '.created')) {
            return 'create';
        }

        if ($new !== [] && $old === []) {
            return 'create';
        }

        if ($old !== [] && $new === []) {
            return 'delete';
        }

        return 'update';
    }

    private function valuesAreEquivalent(mixed $a, mixed $b): bool
    {
        if ($a === $b) {
            return true;
        }

        return json_encode($a, JSON_UNESCAPED_UNICODE) === json_encode($b, JSON_UNESCAPED_UNICODE);
    }

    private function buildFallbackOperationSummary(string $operatorLabel, string $action, string $subjectTitle): string
    {
        $actionText = DberpAuditDisplay::actionLabel($action);
        $unknownSubject = (string) __('dberp.audit_display.subject_unknown');

        if ($subjectTitle !== '' && $subjectTitle !== $unknownSubject) {
            return trim($operatorLabel.' '.$actionText.' 「'.$subjectTitle.'」');
        }

        return trim($operatorLabel.' '.$actionText);
    }

    private function tryResolveStructuralVerbPhrase(string $action): ?string
    {
        if ($action === '' || ! str_contains($action, '.')) {
            return null;
        }

        $segment = strtolower(Str::afterLast($action, '.'));
        $resolved = $this->resolveOperationVerbLabel($segment);
        if ($resolved === null) {
            return null;
        }

        return $resolved.' ';
    }

    private function resolveOperationVerbLabel(string $segment): ?string
    {
        $key = 'dberp.audit_display.operation_verb.'.$segment;
        $label = __($key);
        if ($label !== $key) {
            return trim((string) $label);
        }

        foreach (self::ACTION_VERB_SUFFIX_PRIORITY as $suffix) {
            if ($segment === $suffix || str_ends_with($segment, '_'.$suffix)) {
                $suffixKey = 'dberp.audit_display.operation_verb.'.$suffix;
                $suffixLabel = __($suffixKey);

                if ($suffixLabel !== $suffixKey) {
                    return trim((string) $suffixLabel);
                }
            }
        }

        return null;
    }
}
