<?php

namespace App\Http\Middleware;

use Closure;
use App\Models\AdminUser;
use App\Services\AuditLogger;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\Response;

/**
 * 对 admin_api_resources.is_audit_required=true 的变更类请求，在成功响应后写入审计日志。
 *
 * 若请求已设置 {@see Request::ATTRIBUTE_AUDIT_RECORDED}（业务层已写更细快照），则跳过。
 */
final class RecordAdminApiAudit
{
    public const ATTRIBUTE_AUDIT_RECORDED = 'admin_audit_recorded';

    private const MUTATING_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE'];

    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        if (! $this->shouldRecord($request, $response)) {
            return $response;
        }

        $admin = $request->user();
        if (! $admin instanceof AdminUser) {
            return $response;
        }

        $resource = $this->resolveResource($request);
        if ($resource === null || ! (bool) $resource->is_audit_required) {
            return $response;
        }

        $targetId = $this->resolveTargetId($request);
        $actionCode = $this->resolveActionCode((string) $resource->code);

        AuditLogger::recordForAdmin(
            $admin,
            $request,
            moduleCode: (string) $resource->module_code,
            actionCode: $actionCode,
            targetType: (string) $resource->code,
            targetId: $targetId,
            beforeJson: null,
            afterJson: [
                'http_method' => $request->method(),
                'route_name' => $this->normalizeRouteName((string) ($request->route()?->getName() ?? '')),
                'status' => $response->getStatusCode(),
                'payload' => $this->sanitizedPayload($request),
            ],
        );

        return $response;
    }

    private static function isAuditAlreadyRecorded(Request $request): bool
    {
        foreach (self::auditRecordedRequests($request) as $candidate) {
            if ($candidate->attributes->get(self::ATTRIBUTE_AUDIT_RECORDED) === true) {
                return true;
            }
        }

        return false;
    }

    /** @return list<Request> */
    private static function auditRecordedRequests(Request $request): array
    {
        $requests = [$request];

        try {
            $resolved = request();
            if ($resolved !== $request) {
                $requests[] = $resolved;
            }
        } catch (\Throwable) {
        }

        return $requests;
    }

    private function shouldRecord(Request $request, Response $response): bool
    {
        if (self::isAuditAlreadyRecorded($request)) {
            return false;
        }

        if (! in_array(strtoupper($request->method()), self::MUTATING_METHODS, true)) {
            return false;
        }

        $status = $response->getStatusCode();

        return $status >= 200 && $status < 300;
    }

    private function resolveResource(Request $request): ?object
    {
        $routeName = $request->route()?->getName();
        if (! is_string($routeName) || $routeName === '') {
            return null;
        }

        return DB::table('admin_api_resources')
            ->where('route_name', $this->normalizeRouteName($routeName))
            ->where('status', 1)
            ->first(['code', 'module_code', 'is_audit_required']);
    }

    private function normalizeRouteName(string $routeName): string
    {
        return preg_replace('/^(api\.v1\.admin\.)+/', 'api.v1.admin.', $routeName) ?? $routeName;
    }

    private function resolveActionCode(string $resourceCode): string
    {
        $pos = strrpos($resourceCode, '.');
        if ($pos === false) {
            return $resourceCode;
        }

        return substr($resourceCode, $pos + 1);
    }

    private function resolveTargetId(Request $request): ?string
    {
        $route = $request->route();
        if ($route === null) {
            return null;
        }

        foreach (['batch', 'draw', 'transfer_no', 'player', 'admin_user', 'admin_role', 'id', 'play_code', 'number_4d', 'key'] as $key) {
            $value = $route->parameter($key);
            if ($value === null) {
                continue;
            }

            if (is_object($value) && method_exists($value, 'getKey')) {
                return (string) $value->getKey();
            }

            return (string) $value;
        }

        return null;
    }

    /**
     * @return array<string, mixed>|null
     */
    private function sanitizedPayload(Request $request): ?array
    {
        $data = $request->except([
            'password',
            'password_confirmation',
            'current_password',
            'token',
        ]);

        return $data === [] ? null : $data;
    }
}
