<?php

namespace Kylin\User\Services;

use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Hash;
use Kylin\Core\Exceptions\BusinessException;
use Kylin\Core\Enums\CoreErrorCode;
use Kylin\User\Enums\UserStatus;
use Kylin\User\Models\User;
use Kylin\User\Models\UserProfile;

/**
 * 用户服务
 *
 * 提供用户管理核心业务逻辑：列表查询、创建、更新、删除、状态管理。
 *
 * 合规要求：
 *  - 依据等保 2.0 (GB/T 22239) 8.1.4.6，用户增删改操作需记录审计日志
 *  - 依据《个人信息保护法》第五十一条，敏感字段加密存储
 */
class UserService
{
    public function list(array $filters = [], int $perPage = 15): LengthAwarePaginator
    {
        $query = User::query()->with(['profile', 'roles']);

        if (! empty($filters['keyword'])) {
            $query->where(function ($q) use ($filters) {
                $q->where('name', 'like', "%{$filters['keyword']}%")
                    ->orWhere('email', 'like', "%{$filters['keyword']}%")
                    ->orWhere('phone', 'like', "%{$filters['keyword']}%");
            });
        }

        if (isset($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        return $query->orderByDesc('id')->paginate($perPage);
    }

    public function create(array $data): User
    {
        $data['password'] = Hash::make($data['password']);
        $data['status'] = $data['status'] ?? UserStatus::Active;

        $user = User::create($data);

        if (! empty($data['roles'])) {
            $user->assignRole($data['roles']);
        }

        // 审计日志：创建用户
        $this->audit('user_created', get_class($user), $user->id, [
            'name' => $user->name,
            'email' => $user->email,
            'phone' => $this->maskPhone($user->phone),
            'status' => $user->status->label(),
        ]);

        return $user;
    }

    public function update(User $user, array $data): User
    {
        $oldValues = [
            'name' => $user->name,
            'email' => $user->email,
            'status' => $user->status->label(),
        ];

        if (! empty($data['password'])) {
            $data['password'] = Hash::make($data['password']);
            $data['password_changed_at'] = now();
        } else {
            unset($data['password']);
        }

        $user->update($data);

        if (isset($data['roles'])) {
            $user->roles()->detach();
            $user->assignRole($data['roles']);
        }

        $updatedUser = $user->fresh();

        $newValues = [
            'name' => $updatedUser->name,
            'email' => $updatedUser->email,
            'status' => $updatedUser->status->label(),
        ];

        // 审计日志：更新用户
        $this->audit('user_updated', get_class($user), $user->id, $newValues, $oldValues);

        return $updatedUser;
    }

    public function delete(User $user): bool
    {
        $payload = [
            'name' => $user->name,
            'email' => $user->email,
            'status' => $user->status->label(),
        ];

        $result = $user->delete();

        // 审计日志：删除用户
        $this->audit('user_deleted', get_class($user), $user->id, null, $payload);

        return $result;
    }

    public function updateProfile(User $user, array $data): UserProfile
    {
        $profile = UserProfile::updateOrCreate(
            ['user_id' => $user->id],
            $data,
        );

        // 审计日志：更新用户资料
        $this->audit('user_profile_updated', UserProfile::class, $profile->id, [
            'user_id' => $user->id,
            'user_name' => $user->name,
        ]);

        return $profile;
    }

    public function changePassword(User $user, string $currentPassword, string $newPassword): bool
    {
        if (! Hash::check($currentPassword, $user->password)) {
            throw new BusinessException(CoreErrorCode::ValidationError, '当前密码错误');
        }

        $user->update([
            'password' => Hash::make($newPassword),
            'password_changed_at' => now(),
        ]);

        // 审计日志：修改密码
        $this->audit('user_password_changed', get_class($user), $user->id, [
            'name' => $user->name,
            'email' => $user->email,
        ]);

        return true;
    }

    public function toggleStatus(User $user): User
    {
        $oldStatus = $user->status->label();
        $newStatus = $user->status === UserStatus::Active
            ? UserStatus::Disabled
            : UserStatus::Active;

        $user->update(['status' => $newStatus]);
        $updatedUser = $user->fresh();

        // 审计日志：状态变更
        $this->audit('user_status_changed', get_class($user), $user->id, [
            'name' => $user->name,
            'old_status' => $oldStatus,
            'new_status' => $updatedUser->status->label(),
        ]);

        return $updatedUser;
    }

    /**
     * 写入审计日志
     *
     * 依据等保 2.0 (GB/T 22239) 8.1.4.6：
     * 用户增删改操作需记录审计日志。
     *
     * 审计日志写入失败不中断业务，仅记录 warning 日志。
     */
    private function audit(string $event, string $modelType, int|string $modelId, ?array $newValues = null, ?array $oldValues = null): void
    {
        if (! class_exists(\Kylin\AuditLog\Models\AuditLog::class)) {
            return;
        }

        try {
            \Kylin\AuditLog\Models\AuditLog::create([
                'event' => $event,
                'model_type' => $modelType,
                'model_id' => $modelId,
                'old_values' => $oldValues,
                'new_values' => $newValues,
                'user_id' => auth()->id(),
                'ip' => request()->ip(),
                'url' => request()->fullUrl(),
                'tag' => 'user',
            ]);
        } catch (\Throwable $e) {
            logger()->warning('用户操作审计日志写入失败', [
                'event' => $event,
                'model_type' => $modelType,
                'model_id' => $modelId,
                'error' => $e->getMessage(),
            ]);
        }
    }

    /**
     * 手机号脱敏
     *
     * 依据《个人信息保护法》第五十一条，对敏感信息脱敏处理。
     */
    private function maskPhone(?string $phone): ?string
    {
        if (empty($phone) || mb_strlen($phone) < 7) {
            return $phone;
        }

        return mb_substr($phone, 0, 3) . '****' . mb_substr($phone, -4);
    }
}