<?php

namespace app\process;

use app\common\model\MallItem;
use app\common\model\MallPlayxOrder;
use app\common\model\MallPlayxUserAsset;
use GuzzleHttp\Client;
use Workerman\Timer;
use Workerman\Worker;

/**
 * PlayX 积分商城闭环任务
 * - 轮询交易终态（ACCEPTED -> COMPLETED/REJECTED）
 * - 对可重试失败进行重发（依赖 external_transaction_id 幂等）
 */
class PlayxJobs
{
    protected Client $http;

    public function __construct()
    {
        // 确保定时任务只在一个 worker 上运行
        if (!Worker::getAllWorkers()) {
            return;
        }

        $this->http = new Client([
            'timeout' => 20,
            'http_errors' => false,
        ]);

        Timer::add(60, [$this, 'pollTransactionStatus']);
        Timer::add(60, [$this, 'retryFailedGrants']);
    }

    /**
     * 轮询：已 accepted 的订单，查询终态
     */
    public function pollTransactionStatus(): void
    {
        $baseUrl = strval(config('playx.api.base_url', ''));
        if ($baseUrl === '') {
            return;
        }

        $path = strval(config('playx.api.transaction_status_url', '/api/v1/transaction/status'));
        $url = rtrim($baseUrl, '/') . $path;

        $list = MallPlayxOrder::where('grant_status', MallPlayxOrder::GRANT_ACCEPTED)
            ->where('status', MallPlayxOrder::STATUS_PENDING)
            ->order('id', 'desc')
            ->limit(50)
            ->select();

        foreach ($list as $order) {
            /** @var MallPlayxOrder $order */
            try {
                $res = $this->http->get($url, [
                    'query' => [
                        'externalTransactionId' => $order->external_transaction_id,
                    ],
                ]);

                $data = json_decode(strval($res->getBody()), true) ?? [];
                $pxStatus = $data['status'] ?? '';

                if ($pxStatus === MallPlayxOrder::STATUS_COMPLETED) {
                    $order->status = MallPlayxOrder::STATUS_COMPLETED;
                    $order->save();
                    continue;
                }

                if ($pxStatus === 'FAILED' || $pxStatus === MallPlayxOrder::STATUS_REJECTED) {
                    // 仅在从 PENDING 转 REJECTED 时退分，避免重复入账
                    $order->status = MallPlayxOrder::STATUS_REJECTED;
                    $order->grant_status = MallPlayxOrder::GRANT_FAILED_FINAL;
                    $order->fail_reason = strval($data['message'] ?? 'PlayX transaction failed');
                    $order->save();
                    $this->refundPoints($order);
                    continue;
                }

                // 其他情况视为仍在队列/PENDING，保持不变
            } catch (\Throwable $e) {
                // 查询失败不影响状态，下一轮重试
            }
        }
    }

    /**
     * 重试：NOT_SENT / FAILED_RETRYABLE（按 retry_count 间隔）
     */
    public function retryFailedGrants(): void
    {
        $baseUrl = strval(config('playx.api.base_url', ''));
        if ($baseUrl === '') {
            return;
        }

        $bonusPath = strval(config('playx.api.bonus_grant_url', '/api/v1/bonus/grant'));
        $withdrawPath = strval(config('playx.api.balance_credit_url', '/api/v1/balance/credit'));
        $bonusUrl = rtrim($baseUrl, '/') . $bonusPath;
        $withdrawUrl = rtrim($baseUrl, '/') . $withdrawPath;

        $maxRetry = 3;
        $list = MallPlayxOrder::whereIn('grant_status', [
                MallPlayxOrder::GRANT_NOT_SENT,
                MallPlayxOrder::GRANT_FAILED_RETRYABLE,
            ])
            ->where('status', MallPlayxOrder::STATUS_PENDING)
            ->where('retry_count', '<', $maxRetry)
            ->order('id', 'desc')
            ->limit(50)
            ->select();

        foreach ($list as $order) {
            /** @var MallPlayxOrder $order */
            $allow = $this->allowRetryByInterval($order);
            if (!$allow) {
                continue;
            }

            $order->retry_count = intval($order->retry_count ?? 0) + 1;

            try {
                $this->sendGrantByOrder($order, $bonusUrl, $withdrawUrl, $maxRetry);
            } catch (\Throwable $e) {
                $order->fail_reason = $e->getMessage();
                if (intval($order->retry_count) >= $maxRetry) {
                    $order->grant_status = MallPlayxOrder::GRANT_FAILED_FINAL;
                    $order->status = MallPlayxOrder::STATUS_REJECTED;
                    $order->save();
                    $this->refundPoints($order);
                } else {
                    $order->grant_status = MallPlayxOrder::GRANT_FAILED_RETRYABLE;
                    $order->save();
                }
            }
        }
    }

    private function allowRetryByInterval(MallPlayxOrder $order): bool
    {
        if ($order->grant_status === MallPlayxOrder::GRANT_NOT_SENT) {
            return true;
        }

        $retryCount = intval($order->retry_count ?? 0);
        $updatedAt = intval($order->update_time ?? 0);
        $diff = time() - $updatedAt;

        // retry_count: 已失败重试次数
        // 0 -> 下次重试：等待 1min
        // 1 -> 下次重试：等待 5min
        // 2 -> 下次重试：等待 15min
        if ($retryCount === 0 && $diff >= 60) {
            return true;
        }
        if ($retryCount === 1 && $diff >= 300) {
            return true;
        }
        if ($retryCount >= 2 && $diff >= 900) {
            return true;
        }

        return false;
    }

    private function sendGrantByOrder(MallPlayxOrder $order, string $bonusUrl, string $withdrawUrl, int $maxRetry): void
    {
        $item = null;
        if ($order->mallItem) {
            $item = $order->mallItem;
        } else {
            $item = MallItem::where('id', $order->mall_item_id)->find();
        }

        if ($order->type === MallPlayxOrder::TYPE_BONUS) {
            $rewardName = $item ? strval($item->title) : '';
            $category = $item ? strval($item->category) : 'daily';
            $categoryTitle = $item ? strval($item->category_title) : '';
            $multiplier = intval($order->multiplier ?? 0);
            if ($multiplier <= 0) {
                $multiplier = 1;
            }

            $requestId = 'mall_retry_bonus_' . uniqid();
            $res = $this->http->post($bonusUrl, [
                'json' => [
                    'request_id' => $requestId,
                    'externalTransactionId' => $order->external_transaction_id,
                    'user_id' => $order->user_id,
                    'amount' => $order->amount,
                    'rewardName' => $rewardName,
                    'category' => $category,
                    'categoryTitle' => $categoryTitle,
                    'multiplier' => $multiplier,
                ],
            ]);

            $data = json_decode(strval($res->getBody()), true) ?? [];
            if ($res->getStatusCode() === 200 && ($data['status'] ?? '') === 'accepted') {
                $order->grant_status = MallPlayxOrder::GRANT_ACCEPTED;
                $order->playx_transaction_id = strval($data['playx_transaction_id'] ?? '');
                $order->save();
                return;
            }

            $order->fail_reason = strval($data['message'] ?? 'PlayX bonus grant not accepted');
            if (intval($order->retry_count) >= $maxRetry) {
                $order->grant_status = MallPlayxOrder::GRANT_FAILED_FINAL;
                $order->status = MallPlayxOrder::STATUS_REJECTED;
                $order->save();
                $this->refundPoints($order);
                return;
            }

            $order->grant_status = MallPlayxOrder::GRANT_FAILED_RETRYABLE;
            $order->save();
            return;
        }

        if ($order->type === MallPlayxOrder::TYPE_WITHDRAW) {
            $multiplier = intval($order->multiplier ?? 0);
            if ($multiplier <= 0) {
                $multiplier = 1;
            }
            $requestId = 'mall_retry_withdraw_' . uniqid();
            $res = $this->http->post($withdrawUrl, [
                'json' => [
                    'request_id' => $requestId,
                    'externalTransactionId' => $order->external_transaction_id,
                    'user_id' => $order->user_id,
                    'amount' => $order->amount,
                    'multiplier' => $multiplier,
                ],
            ]);

            $data = json_decode(strval($res->getBody()), true) ?? [];
            if ($res->getStatusCode() === 200 && ($data['status'] ?? '') === 'accepted') {
                $order->grant_status = MallPlayxOrder::GRANT_ACCEPTED;
                $order->playx_transaction_id = strval($data['playx_transaction_id'] ?? '');
                $order->save();
                return;
            }

            $order->fail_reason = strval($data['message'] ?? 'PlayX balance credit not accepted');
            if (intval($order->retry_count) >= $maxRetry) {
                $order->grant_status = MallPlayxOrder::GRANT_FAILED_FINAL;
                $order->status = MallPlayxOrder::STATUS_REJECTED;
                $order->save();
                $this->refundPoints($order);
                return;
            }

            $order->grant_status = MallPlayxOrder::GRANT_FAILED_RETRYABLE;
            $order->save();
            return;
        }

        // PHYSICAL 目前由后台手工发货/驳回，不参与 PlayX 发放重试
    }

    private function refundPoints(MallPlayxOrder $order): void
    {
        if ($order->points_cost <= 0) {
            return;
        }
        $asset = MallPlayxUserAsset::where('playx_user_id', $order->user_id)->find();
        if (!$asset) {
            return;
        }

        // 已从用户可用积分中扣除，本次失败退回可用积分
        $asset->available_points += intval($order->points_cost);
        $asset->save();
    }
}

