<?php

namespace app\process;

use app\common\library\MallBonusGrantPush;
use app\common\model\MallOrder;
use app\common\model\MallUserAsset;
use GuzzleHttp\Client;
use Workerman\Timer;
use Workerman\Worker;

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

    public function onWorkerStart(Worker $worker): void
    {
        $this->http = new Client([
            'timeout' => 20,
            'http_errors' => false,
        ]);

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

    protected function getHttp(): ?Client
    {
        return $this->http;
    }

    /**
     * 轮询：已 accepted 的订单，查询终态
     */
    public function pollTransactionStatus(): void
    {
        $http = $this->getHttp();
        if ($http === null) {
            return;
        }

        $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 = MallOrder::where('type', MallOrder::TYPE_BONUS)
            ->where('grant_status', MallOrder::GRANT_ACCEPTED)
            ->where('status', MallOrder::STATUS_PENDING)
            ->order('id', 'desc')
            ->limit(50)
            ->select();

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

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

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

                if ($pxStatus === 'FAILED' || $pxStatus === MallOrder::STATUS_REJECTED) {
                    // 仅在从 PENDING 转 REJECTED 时退分，避免重复入账
                    $order->status = MallOrder::STATUS_REJECTED;
                    $order->grant_status = MallOrder::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;
        }

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

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

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

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

    private function allowRetryByInterval(MallOrder $order): bool
    {
        if ($order->grant_status === MallOrder::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(MallOrder $order, int $maxRetry): void
    {
        if ($order->type !== MallOrder::TYPE_BONUS) {
            return;
        }

        $result = MallBonusGrantPush::push($order);
        if ($result['ok']) {
            $order->grant_status = MallOrder::GRANT_ACCEPTED;
            $order->status = MallOrder::STATUS_COMPLETED;
            $order->update_time = time();
            $order->save();

            return;
        }

        $order->fail_reason = $result['message'];
        if (intval($order->retry_count) >= $maxRetry) {
            $order->grant_status = MallOrder::GRANT_FAILED_FINAL;
            $order->save();

            return;
        }

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

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

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

