<?php

/**
 * 文件功能：用户积分统一变更服务
 * 所有修改 exp_num（经验）、jjb（金币）、meili（魅力） 的操作必须经由此服务，
 * 禁止在 Controller 中直接操作 User 属性并 save()。
 * 本服务负责：原子性更新用户属性、写入流水记录、提供统计与排行数据。
 *
 * @author   ChatRoom Laravel
 *
 * @version  1.0.0
 */

namespace App\Services;

use App\Enums\CurrencySource;
use App\Models\User;
use App\Models\UserCurrencyLog;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class UserCurrencyService
{
    /**
     * currency 标识与 users 表字段名的映射关系。
     * 以后新增货币类型，在此加一行即可。
     */
    private const FIELD_MAP = [
        'exp' => 'exp_num',
        'gold' => 'jjb',
        'charm' => 'meili',
    ];

    /**
     * 统一变更用户货币属性并写入流水记录。
     * 使用数据库事务保证原子性：用户属性更新 + 流水写入同时成功或同时回滚。
     *
     * @param  User  $user  目标用户
     * @param  string  $currency  货币类型（'exp' / 'gold' / 'charm'）
     * @param  int  $amount  变更量，正数增加，负数扣除
     * @param  CurrencySource  $source  来源活动枚举
     * @param  string  $remark  备注说明
     * @param  int|null  $roomId  所在房间 ID（可选）
     */
    public function change(
        User $user,
        string $currency,
        int $amount,
        CurrencySource $source,
        string $remark = '',
        ?int $roomId = null,
    ): void {
        if ($amount === 0) {
            return; // 变更量为 0 不写记录
        }

        $field = self::FIELD_MAP[$currency] ?? null;
        if (! $field) {
            return; // 未知货币类型，静默忽略（不抛异常，避免影响主流程）
        }

        DB::transaction(function () use ($user, $currency, $amount, $source, $remark, $roomId, $field) {
            // 原子性更新用户属性（用 increment/decrement 防并发竞态）
            if ($amount > 0) {
                $user->increment($field, $amount);
            } else {
                // 扣除时不让余额低于 0
                $user->decrement($field, min(abs($amount), $user->$field ?? 0));
            }

            // 重新读取最新余额（避免缓存脏数据）
            $balanceAfter = (int) $user->fresh()->$field;

            // 写入流水记录（快照当前用户名，排行 JOIN 时再取最新名）
            UserCurrencyLog::create([
                'user_id' => $user->id,
                'username' => $user->username,
                'currency' => $currency,
                'amount' => $amount,
                'balance_after' => $balanceAfter,
                'source' => $source->value,
                'remark' => $remark,
                'room_id' => $roomId,
            ]);
        });
    }

    /**
     * 批量变更多个用户的货币属性（适用于自动存点：一次操作多人）。
     * 每位用户仍独立走事务，单人失败不影响其他人。
     *
     * @param  array  $items  [['user' => User, 'changes' => ['exp'=>1,'gold'=>2]], ...]
     */
    public function batchChange(array $items, CurrencySource $source, ?int $roomId = null): void
    {
        foreach ($items as $item) {
            $user = $item['user'];
            $changes = $item['changes'] ?? [];
            foreach ($changes as $currency => $amount) {
                $this->change($user, $currency, (int) $amount, $source, '', $roomId);
            }
        }
    }

    /**
     * 查询某日各来源活动的产出统计（后台统计页面使用）。
     * 返回格式：Collection of stdClass { source, currency, total_amount, participant_count }
     *
     * @param  string|null  $date  日期字符串如 '2026-02-28'，默认今日
     */
    public function activityStats(?string $date = null): Collection
    {
        $date = $date ?? today()->toDateString();

        return UserCurrencyLog::query()
            ->whereDate('created_at', $date)
            ->selectRaw('source, currency, SUM(amount) as total_amount, COUNT(DISTINCT user_id) as participant_count')
            ->groupBy('source', 'currency')
            ->orderBy('currency')
            ->orderByRaw('ABS(SUM(amount)) DESC')
            ->get();
    }

    /**
     * 今日排行榜（按 user_id 聚合，展示最新用户名）。
     * 只统计正向变更（amount > 0），不因消耗而扣分。
     *
     * @param  string  $currency  'exp' | 'gold' | 'charm'
     * @param  int  $limit  返回条数
     * @param  string|null  $date  日期，默认今日
     */
    public function todayLeaderboard(string $currency, int $limit = 20, ?string $date = null): Collection
    {
        $date = $date ?? today()->toDateString();

        return UserCurrencyLog::query()
            ->whereDate('created_at', $date)
            ->where('currency', $currency)
            // 计算净收益，包含正向与负向消耗
            ->selectRaw('user_id, SUM(amount) as total')
            ->groupBy('user_id')
            ->havingRaw('SUM(amount) > 0') // 只有今日净收益为正数才能上榜
            ->orderByRaw('SUM(amount) DESC')
            ->limit($limit)
            ->get()
            ->map(function ($row) {
                // JOIN 取最新用户名（避免改名后显示旧名）
                $user = User::select('id', 'username', 'user_level', 'sex', 'headface')
                    ->find($row->user_id);

                return (object) [
                    'user_id' => $row->user_id,
                    'username' => $user?->username ?? '未知用户',
                    'level' => $user?->user_level ?? 0,
                    'sex' => $user?->sex ?? 1,
                    'headface' => $user?->headface ?? '1.gif',
                    'total' => $row->total,
                ];
            });
    }

    /**
     * 用户个人流水明细（用户查询自己的日志）。
     *
     * @param  int  $userId  用户 ID
     * @param  string|null  $currency  为 null 时返回所有货币类型
     * @param  int  $days  查询最近多少天
     */
    public function userLogs(int $userId, ?string $currency = null, int $days = 7): Collection
    {
        return UserCurrencyLog::query()
            ->where('user_id', $userId)
            ->when($currency, fn ($q) => $q->where('currency', $currency))
            ->where('created_at', '>=', now()->subDays($days))
            ->orderByDesc('created_at')
            ->limit(200)
            ->get();
    }

    /**
     * 货币类型中文名映射（用于视图展示）。
     */
    public static function currencyLabel(string $currency): string
    {
        return match ($currency) {
            'exp' => '经验',
            'gold' => '金币',
            'charm' => '魅力',
            default => $currency,
        };
    }
}
