<?php

declare(strict_types=1);

namespace app\common\library;

use app\common\model\MallDailyPush;
use app\common\model\MallUserAsset;
use ba\Random;
use Throwable;

/**
 * 基于历史每日推送记录回补/同步用户资产主信息。
 */
class MallDailyPushBackfill
{
    /**
     * @return array<string, mixed>
     */
    public function backfill(?string $dateFrom, ?string $dateTo, int $limit = 0, bool $dryRun = false): array
    {
        $query = MallDailyPush::order('create_time', 'desc')->order('id', 'desc');
        if ($dateFrom !== null && $dateFrom !== '') {
            $query->where('date', '>=', $dateFrom);
        }
        if ($dateTo !== null && $dateTo !== '') {
            $query->where('date', '<=', $dateTo);
        }

        $rows = $query->select();
        $seenUserIds = [];

        $stats = [
            'dry_run' => $dryRun,
            'date_from' => $dateFrom,
            'date_to' => $dateTo,
            'limit' => $limit,
            'scanned_rows' => 0,
            'target_users' => 0,
            'processed_users' => 0,
            'created_users' => 0,
            'updated_users' => 0,
            'unchanged_users' => 0,
            'failed_users' => 0,
            'errors' => [],
        ];

        foreach ($rows as $row) {
            $stats['scanned_rows']++;
            $playxUserId = trim(strval($row->user_id ?? ''));
            if ($playxUserId === '' || isset($seenUserIds[$playxUserId])) {
                continue;
            }
            $seenUserIds[$playxUserId] = true;
            $stats['target_users']++;

            if ($limit > 0 && $stats['target_users'] > $limit) {
                break;
            }

            $username = trim(strval($row->username ?? ''));
            try {
                $result = $this->ensureAssetForPlayx($playxUserId, $username, $dryRun);
                $stats['processed_users']++;
                if ($result['created']) {
                    $stats['created_users']++;
                } elseif ($result['updated']) {
                    $stats['updated_users']++;
                } else {
                    $stats['unchanged_users']++;
                }
            } catch (Throwable $e) {
                $stats['failed_users']++;
                $stats['errors'][] = [
                    'user_id' => $playxUserId,
                    'message' => $e->getMessage(),
                ];
            }
        }

        return $stats;
    }

    /**
     * @return array{created:bool,updated:bool}
     */
    private function ensureAssetForPlayx(string $playxUserId, string $username, bool $dryRun): array
    {
        $asset = MallUserAsset::where('playx_user_id', $playxUserId)->find();
        if ($asset) {
            $updated = false;
            $uname = trim($username);
            if ($uname !== '' && strval($asset->username ?? '') !== $uname) {
                $asset->username = $uname;
                $updated = true;
            }
            if ($updated && !$dryRun) {
                $asset->save();
            }
            return ['created' => false, 'updated' => $updated];
        }

        $effectiveUsername = trim($username);
        if ($effectiveUsername === '') {
            $effectiveUsername = 'playx_' . $playxUserId;
        }

        $byName = MallUserAsset::where('username', $effectiveUsername)->find();
        if ($byName) {
            $updated = strval($byName->playx_user_id ?? '') !== $playxUserId;
            if ($updated && !$dryRun) {
                $byName->playx_user_id = $playxUserId;
                $byName->save();
            }
            return ['created' => false, 'updated' => $updated];
        }

        if ($dryRun) {
            return ['created' => true, 'updated' => false];
        }

        $phone = $this->buildTempPhone();
        if ($phone === null) {
            throw new \RuntimeException('Failed to allocate phone for playx user');
        }
        $pwd = hash_password(Random::build('alnum', 16));
        $now = time();
        $created = MallUserAsset::create([
            'playx_user_id' => $playxUserId,
            'username' => $effectiveUsername,
            'phone' => $phone,
            'password' => $pwd,
            'admin_id' => 0,
            'locked_points' => 0,
            'available_points' => 0,
            'today_limit' => 0,
            'today_claimed' => 0,
            'today_limit_date' => null,
            'create_time' => $now,
            'update_time' => $now,
        ]);

        if (!$created) {
            throw new \RuntimeException('Failed to create mall_user_asset');
        }
        return ['created' => true, 'updated' => false];
    }

    private function buildTempPhone(): ?string
    {
        for ($i = 0; $i < 8; $i++) {
            $candidate = '13' . str_pad(strval(mt_rand(0, 999999999)), 9, '0', STR_PAD_LEFT);
            if (!MallUserAsset::where('phone', $candidate)->find()) {
                return $candidate;
            }
        }
        return null;
    }
}

