<?php

declare(strict_types=1);

namespace HyperfTest\Cases\Unit\Service;

use App\Model\OperationLog;
use App\Service\OperationLogService;
use HyperfTest\TestCase;
use HyperfTest\Traits\AuthenticatedTestTrait;

/**
 * OperationLogService 单元测试
 *
 * 验证操作日志的异步写入和辅助方法
 *
 * @internal
 * @coversNothing
 */
class OperationLogServiceTest extends TestCase
{
    use AuthenticatedTestTrait;

    /**
     * 在子协程中执行回调，等待其退出（含 defer 执行）
     *
     * co-phpunit 下所有测试共享同一顶层协程，
     * Coroutine::defer 的回调要到协程退出才执行。
     * 此方法创建子协程并 join 等待，确保 defer 回调完成后再继续。
     */
    protected function runInChildCoroutineAndWait(callable $callback): void
    {
        $cid = \Swoole\Coroutine::create($callback);
        if ($cid !== false) {
            \Swoole\Coroutine::join([$cid]);
        }
    }

    public function test_log_creates_record_in_coroutine(): void
    {
        $action = 'test.unit_' . uniqid();

        $this->runInChildCoroutineAndWait(static function () use ($action): void {
            OperationLogService::log(
                user_id: 1,
                action: $action,
                target_type: 'user',
                target_id: 99,
                description: '单元测试操作日志',
                detail: ['key' => 'value'],
                ip: '127.0.0.1',
            );
        });

        // join 等待子协程退出后 defer 已执行，记录已写入
        $record = OperationLog::query()->where('action', $action)->first();

        $this->assertNotNull($record);
        $this->assertSame(1, $record->user_id);
        $this->assertSame($action, $record->action);
        $this->assertSame('user', $record->target_type);
        $this->assertSame(99, $record->target_id);
        $this->assertSame('单元测试操作日志', $record->description);
        $this->assertSame(['key' => 'value'], $record->detail);
        $this->assertSame('127.0.0.1', $record->ip);

        // 清理测试数据
        OperationLog::query()->where('id', $record->id)->delete();
    }

    public function test_log_with_null_detail_and_ip(): void
    {
        $action = 'test.null_detail_' . uniqid();

        $this->runInChildCoroutineAndWait(static function () use ($action): void {
            OperationLogService::log(
                user_id: 2,
                action: $action,
                target_type: 'auth',
                target_id: null,
                description: '测试空 detail 和 ip',
            );
        });

        $record = OperationLog::query()->where('action', $action)->first();

        $this->assertNotNull($record);
        $this->assertSame(2, $record->user_id);
        $this->assertNull($record->target_id);
        $this->assertNull($record->detail);
        $this->assertNull($record->ip);

        // 清理
        OperationLog::query()->where('id', $record->id)->delete();
    }

    public function test_log_does_not_throw_on_failure(): void
    {
        // 验证 log 方法在写入失败时不抛异常（仅记录错误日志）
        // 使用超长 action 触发数据库约束失败
        $long_action = str_repeat('a', 100); // 超过 VARCHAR(50) 限制

        $this->runInChildCoroutineAndWait(static function () use ($long_action): void {
            OperationLogService::log(
                user_id: 1,
                action: $long_action,
                target_type: 'test',
                target_id: null,
                description: '不应抛异常',
            );
        });

        // 如果到达这里说明没有抛异常
        $this->assertTrue(true);
    }
}
