<?php

namespace App\Services;

use App\Models\Cao;
use App\Models\CaoHistory;
use App\Models\PcIssue;
use App\Models\Prediction;
use App\Services\BaseService;
use App\Models\Issue;
use App\Models\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

use App\Services\GameplayRuleService;
use App\Constants\GameplayRuleEnum;
use App\Http\Controllers\admin\Lottery;
use App\Services\KeyboardService;
use App\Services\LotteryImageService;
use Telegram\Bot\FileUpload\InputFile;
use App\Jobs\SendTelegramMessageJob;
use App\Jobs\SendTelegramGroupMessageJob;

/**
 * 投注
 */
class IssueService extends BaseService
{

    const COUNTDOWN_TO_CLOSING_THE_MARKET = 60;//提前xx秒封盘
    const PLAYNOW_KENO_URL = 'https://www.playnow.com/services2/keno/nextdraw';
    const PLAYNOW_SOURCE_TIMEZONE = 'America/Vancouver';
    const BEIJING_TIMEZONE = 'Asia/Shanghai';
    const PLAYNOW_DRAW_INTERVAL_SECONDS = 210;


    public static function init($telegram, $data, $chatId, $firstName, $messageId): void
    {
        switch ($data) {
            default:
                //查看开奖历史图片
                $pattern = "/^showLotteryHistory@@\d+$/";
                if (preg_match($pattern, $data)) {
                    $id = preg_replace('/^showLotteryHistory@@/', '', $data);
                    IssueService::sendLotteryImage($chatId, $id);
                }
                break;
        }

    }


    /**
     * @description: 模型
     * @return {string}
     */
    public static function model(): string
    {
        return Issue::class;
    }

    /**
     * @description: 枚举
     * @return {*}
     */
    public static function enum(): string
    {
        return '';
    }

    /**
     * @description: 获取查询条件
     * @param {array} $search 查询内容
     * @return {array}
     */
    public static function getWhere(array $search = []): array
    {
        $where = [];
        if (isset($search['issue_no']) && !empty($search['issue_no'])) {
            $where[] = ['issue_no', '=', $search['issue_no']];
        }
        if (isset($search['id']) && !empty($search['id'])) {
            $where[] = ['id', '=', $search['id']];
        }
        if (isset($search['status']) && $search['status'] != '') {
            $where[] = ['status', '=', $search['status']];
        }
        if (isset($search['abnormal']) && !empty($search['abnormal'])) {
            $where[] = ['end_time', '<', date('Y-m-d H:i:s', time() - 1800)];
            $where[] = ['status', '!=', self::model()::STATUS_DRAW];
        }
        if (!empty($search['start_time'])) {
            $where[] = ['end_time', '>=', $search['start_time'].' 00:00:00'];
        }
        if (!empty($search['end_time'])) {
            $where[] = ['end_time', '<=', $search['end_time'].' 23:59:59'];
        }
        return $where;
    }

    /**
     * @description: 查询单条数据
     * @param array $search
     * @return \App\Models\Coin|null
     */
    public static function findOne(array $search): ?Issue
    {
        return self::model()::where(self::getWhere($search))->first();
    }

    /**
     * @description: 查询所有数据
     * @param array $search
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public static function findAll(array $search = [])
    {
        return self::model()::where(self::getWhere($search))->get();
    }

    /**
     * @description: 分页查询
     * @param array $search
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public static function paginate(array $search = [])
    {
        $limit = isset($search['limit']) ? $search['limit'] : 15;
        $paginator = self::model()::where(self::getWhere($search))->orderBy('issue_no', 'desc')->paginate($limit);
        return ['total' => $paginator->total(), 'data' => $paginator->items()];
    }

    /**
     * @description:
     * @param {*} $params
     * @return {*}
     */
    public static function submit($params = [])
    {
        $result = false;
        $msg['code'] = self::NOT;
        $msg['msg'] = '';

        // 2. 判断是否是更新
        if (!empty($params['id'])) {
            // 更新
            $info = self::findOne(['id' => $params['id']]);
            if (!$info) {
                $msg['msg'] = '期号不存在！';
            } else {
                $result = $info->update($params);
                $id = $params['id'];
            }
        } else {
            // 创建
            $result = $info = self::model()::create($params);
            $id = $result->id;
        }


        if ($result) {
            $msg['code'] = self::YES;
            $msg['msg'] = '设置成功';
            $msg['key'] = $id;
        } else {
            $msg['msg'] = empty($msg['msg']) ? '操作失败' : $msg['msg'];
        }

        return $msg;
    }

    /**
     * @description: 开始下注
     * @param {*} $id
     * @return {*}
     */
    public static function betting($id)
    {
        $info = self::findOne(['id' => $id]);
        if (!$info) {
            return ['code' => self::NOT, 'msg' => '期号不存在'];
        }

        if (!in_array($info->status, [self::model()::STATUS_DRAFT, self::model()::STATUS_BETTING])) {
            return ['code' => self::NOT, 'msg' => '期号状态不正确'];
        }

        $info->status = self::model()::STATUS_BETTING;
        $info->save();


        $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
        $wanFaGuiZeTime = Cache::get("玩法规则推送时间", 0);
        $now = time();
        $wanFaGuiZeTime = intval($wanFaGuiZeTime);
        if ($now - $wanFaGuiZeTime > (60 * 15)) {
            $replyInfo = KeyboardService::findOne(['button' => '玩法规则']);
            if ($replyInfo) {
                $text = $replyInfo->reply;
                $buttons = json_decode($replyInfo->buttons, true);
                $image = $replyInfo->image;
                if ($image) {
                    $image = url($image);
                }
                if (empty($buttons)) {
                    $buttons = self::getOperateButton();
                }
                Cache::put('玩法规则推送时间', time());
                if ($pc28Switch == 0) self::asyncBettingGroupNotice($text, $buttons, $image);
            }
        }
        $replyInfo = KeyboardService::findOne(['button' => '开始下注']);
        if ($replyInfo) {
            $text = $replyInfo->reply;
            $buttons = json_decode($replyInfo->buttons, true);
            $image = $replyInfo->image;
            if ($image) {
                $image = url($image);
            }
            if ($pc28Switch == 0) self::asyncBettingGroupNotice($text, $buttons, $image);
        }

        return ['code' => self::YES, 'msg' => '开始下注'];
    }

    /**
     * @description: 封盘
     * @param {*} $id
     * @return {*}
     */
    public static function closeBetting($id)
    {
        $info = self::findOne(['id' => $id]);
        if (!$info) {
            return ['code' => self::NOT, 'msg' => '期号不存在'];
        }

        if ($info->status != self::model()::STATUS_BETTING) {
            return ['code' => self::NOT, 'msg' => '期号状态不正确'];
        }
        $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
        $info->status = self::model()::STATUS_CLOSE;
        $info->save();
        if ($pc28Switch == 0) {
            $replyInfo = KeyboardService::findOne(['button' => '停止下注']);
            if ($replyInfo) {
                $text = $replyInfo->reply;
                $buttons = json_decode($replyInfo->buttons, true);
                $image = $replyInfo->image;
                if ($image) {
                    $image = url($image);
                }
                //停止下注的信息不发了
//                self::asyncBettingGroupNotice($text, $buttons, $image);
            }
            // 投注情况通知  xxxx期投注统计
            BetService::statNotice($info->issue_no);

            $replyInfo = KeyboardService::findOne(['button' => '封盘开奖']);
            if ($replyInfo) {
                $text = $replyInfo->reply;
                $buttons = json_decode($replyInfo->buttons, true);
                $image = $replyInfo->image;
                if ($image) {
                    $image = url($image);
                }
                // self::bettingGroupNotice($text, $buttons, $image);
                self::asyncBettingGroupNotice($text, $buttons, $image);
            }

        }
        return ['code' => self::YES, 'msg' => '封盘成功'];
    }

    /**
     * @description: 开奖失败
     * @param {*} $id
     * @return {*}
     */
    public static function lotteryDrawFail($id)
    {

        $result = false;
        $msg['code'] = self::NOT;
        $msg['msg'] = '';
        DB::beginTransaction();
        try {

            // 更新
            $info = self::findOne(['id' => $id]);
            if (!$info) {
                $msg['msg'] = '期号不存在！';
            } else {
                $params['status'] = self::model()::STATUS_FAIL;
                $result = $info->update($params);

                BetService::betFail($info->issue_no);
            }

            DB::commit();
            return ['code' => self::YES, 'msg' => '投注已退回'];
        } catch (\Exception $e) {
            DB::rollBack();
            return ['code' => self::NOT, 'msg' => '投注退回失败'];
        }


        if ($result) {
            $msg['code'] = self::YES;
            $msg['msg'] = '设置成功';
        } else {
            $msg['msg'] = empty($msg['msg']) ? '操作失败' : $msg['msg'];
        }

        return $msg;
    }

    /**
     * @description: 开奖
     * @param {*} $id
     * @param {*} $winning_numbers 开奖号码
     * @param {*} $combo 开奖组合
     * @param {*} $recordImage 开奖图片
     * @return {*}
     */
    public static function lotteryDraw($id, $winning_numbers, $combo, $recordImage, $keno = null)
    {
        $info = self::findOne(['id' => $id]);
        if (!$info) {
            return ['code' => self::NOT, 'msg' => '期号不存在'];
        }

        if ($info->status == self::model()::STATUS_DRAW) {
            return ['code' => self::NOT, 'msg' => '期号状态不正确'];
        }

        $winArr = array_map('intval', explode(',', $winning_numbers));
        // 计算中奖
        $awards = self::award(explode(',', $winning_numbers));
        DB::beginTransaction();
        try {
            $info->status = self::model()::STATUS_DRAW;
            $info->winning_numbers = $winning_numbers;
            $info->combo = $combo;
            $kenoJson = self::encodePlayNowKeno($keno);
            if ($kenoJson) {
                $info->keno = $kenoJson;
            }
            $info->saveQuietly();

            Log::channel('issue')->info('官方期开奖写入源头号码', [
                'issue_no' => $info->issue_no,
                'keno_saved' => $kenoJson ? 'yes' : 'no',
                'keno_preview' => $kenoJson ? substr($kenoJson, 0, 80) : '',
            ]);


            $size = in_array("大", $awards);
            $size = $size ? "大" : "小";
            $oddOrEven = in_array("双", $awards);
            $oddOrEven = $oddOrEven ? "双" : "单";
            Prediction::result($info->issue_no, $size, $oddOrEven, $info->winning_numbers);
            Cao::updateData($awards);
            CaoHistory::updateData($awards);

            $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
            $replyInfo = KeyboardService::findOne(['button' => '本期开奖']);
            if ($replyInfo) {
                $text = $replyInfo->reply;
                $text .= "\n";
                $text .= $info->issue_no . "： " . implode('+', explode(',', $winning_numbers)) . "=" . array_sum($winArr) . " " . $combo;

                $buttons = json_decode($replyInfo->buttons, true);
                $image = $replyInfo->image;
                if ($image) {
                    $image = url($image);
                }
                if (empty($buttons)) {
                    $serviceAccount = Config::where('field', 'service_account')->first()->val;
                    $buttons[] = [['text' => lang('✅唯一财务'), 'callback_data' => "", 'url' => "https://t.me/{$serviceAccount}"]];

                }
                // self::bettingGroupNotice($text, $buttons, $image, true);
                if ($pc28Switch == 0) SendTelegramGroupMessageJob::dispatch($text, $buttons, $image, true, "\n", [
                    'message_type' => 'draw_result',
                    'issue_no' => $info->issue_no,
                ])->afterCommit();
            }

            $recordImage = self::lotteryImage($info->issue_no);
            if ($recordImage) {
                // self::bettingGroupNotice('', [], url($recordImage));
                if ($pc28Switch == 0) SendTelegramGroupMessageJob::dispatch('', [], url($recordImage), false, "\n", [
                    'message_type' => 'draw_history_image',
                    'issue_no' => $info->issue_no,
                ])->afterCommit();
            }
            $info->image = $recordImage;
            $info->save();

            BetService::betSettled($info->issue_no, $awards);


            DB::commit();
        } catch (\Exception $e) {
            DB::rollBack();
            $message = "开奖失败:\n{$info->issue_no}\n";
            $message .= $e->getFile() . ':' . $e->getLine();
            $message .= "\n";
            $message .= $e->getMessage() . $winning_numbers;
            Log::error($message);
            return ['code' => self::NOT, 'msg' => '开奖失败', 'error' => $e->getMessage()];
        }
        return ['code' => self::YES, 'msg' => '开奖成功'];
    }


// 虚拟开奖
    public static function fakeLotteryDraw($issue_no, $awards)
    {

        $fake_bet_list = Cache::get('fake_bet_' . $issue_no, []);
        $text = "";

        foreach ($fake_bet_list as $k => $v) {
            $lastStr = self::getLastChar($v['first_name'], 1);
            if (in_array($v['keywords'], $awards)) {
                $amount = (float)$v['amount'];
                $odds = (float)$v['odds'];
                $profit = $amount * $odds;
                if ($profit > 880000) {
                    $profit = 880000; // 单注最高奖金880000
                }
                $item['profit'] = $profit;

                $yl = $profit - $amount;
                if ($k + 1 <= 30) {
                    $text .= "私聊下注 【******" . $lastStr . "】 {$yl}\n";
                }
            } else {
                if ($k + 1 <= 30) {
                    $text .= "私聊下注 【******" . $lastStr . "】 -{$v['amount']}\n";
                }
            }

        }

        return $text;
    }


    /**
     * @description: 获取中奖的奖项
     * @param {*} $winning_numbers
     * @return {*}
     */
    public static function award($winning_numbers)
    {
        $result = [];

        // 组合
        $sum = array_sum($winning_numbers);

        $section = self::getSection($sum); // 总和段位
        $result[] = $section;

        $sumOddEven = self::calculateOddEven($sum); // 总和单双
        $result[] = $sumOddEven;

        $sumSize = self::calculateSumSize($sum);  // 总和大小
        $result[] = $sumSize;
        $sumExtremeSize = self::calculateSumExtremeSize($sum);  // 总和极值
        if ($sumExtremeSize) {
            $result[] = $sumExtremeSize;
        }
        $sumCao = $sum . '操';  // 总和数字
        $result[] = $sumCao;
        $sumCombo = $sumSize . $sumOddEven;  // 总和大小单双组合
        $result[] = $sumCombo;

        $sumBaoZi = self::isBaoZi($winning_numbers[0], $winning_numbers[1], $winning_numbers[2]); // 豹子
        if ($sumBaoZi) {
            $result[] = $sumBaoZi;
        }
        $sumPair = self::isPair($winning_numbers[0], $winning_numbers[1], $winning_numbers[2]); // 对子
        if ($sumPair) {
            $result[] = $sumPair;
        }

        $sumStraight = self::isStraight($winning_numbers[0], $winning_numbers[1], $winning_numbers[2]); // 顺子
        if ($sumStraight) {
            $result[] = $sumStraight;
        }

        $tail = self::getLastDigit($sum); // 总和尾数

        if (!in_array($tail, [0, 9])) {
            $result[] = $tail . '尾'; // 尾数
            $tailOddEven = self::calculateOddEven($tail); // 尾数单双
            $result[] = '尾' . $tailOddEven;

            $tailSize = self::calculateOneSize($tail); // 尾数大小
            $result[] = '尾' . $tailSize;

            $tailCombo = '尾' . $tailSize . $tailOddEven; // 尾数大小单双组合
            $result[] = $tailCombo;
        } else {
            $result[] = $tail . '尾'; // 尾数
        }


        $numA = $winning_numbers[0];    // A球
        $result[] = $numA . 'A';

        $numAOddEven = self::calculateOddEven($numA); // A球单双
        $result[] = 'A' . $numAOddEven;

        $numASize = self::calculateOneSize($numA); // A球大小
        $result[] = 'A' . $numASize;

        $result[] = 'A' . $numASize . $numAOddEven; // A球大小单双组合


        $numB = $winning_numbers[1];    // B球
        $result[] = $numB . 'B';

        $numBOddEven = self::calculateOddEven($numB); // B球
        $result[] = 'B' . $numBOddEven;
        $numBSize = self::calculateOneSize($numB); // B球大小
        $result[] = 'B' . $numBSize;

        $result[] = 'B' . $numBSize . $numBOddEven; // B球大小单双组合

        $numC = $winning_numbers[2];
        $result[] = $numC . 'C';
        $numCOddEven = self::calculateOddEven($numC); // C球单双
        $result[] = 'C' . $numCOddEven;
        $numCSize = self::calculateOneSize($numC); // C球大小
        $result[] = 'C' . $numCSize;

        $result[] = 'C' . $numCSize . $numCOddEven; // C球大小单双组合


        return $result;
    }


    /**
     * @description: 算单双
     * @param {*} $number
     * @return {*}
     */
    public static function calculateOddEven($number)
    {
        if ($number & 1) {
            return GameplayRuleEnum::SINGLE;
        } else {
            return GameplayRuleEnum::DOUBLE;
        }
    }

    /**
     * @description: 总和大小
     * @param {*} $number
     * @return {*}
     */
    public static function calculateSumSize($number)
    {
        if ($number >= GameplayRuleEnum::SUM_BIG) {
            return GameplayRuleEnum::BIG;
        }

        if ($number <= GameplayRuleEnum::SUM_SMALL) {
            return GameplayRuleEnum::SMALL;
        }
    }

    /**
     * @description: 总和极值
     * @param {*} $number
     * @return {*}
     */
    public static function calculateSumExtremeSize($number)
    {
        $result = '';
        if ($number >= GameplayRuleEnum::SUM_EXTREME_BIG) {
            $result = GameplayRuleEnum::EXTREME_BIG;
        }

        if ($number <= GameplayRuleEnum::SUM_EXTREME_SMALL) {
            $result = GameplayRuleEnum::EXTREME_SMALL;
        }

        return $result;
    }

    /**
     * @description: 豹子
     * @param {int} $a
     * @param {int} $b
     * @param {int} $c
     * @return {*}
     */
    public static function isBaoZi(int $a, int $b, int $c)
    {
        $result = '';
        if ($a === $b && $b === $c) {
            $result = GameplayRuleEnum::BAO_ZI;
        }

        return $result;
    }

    /**
     * @description: 对子
     * @param {int} $a
     * @param {int} $b
     * @param {int} $c
     * @return {*}
     */
    public static function isPair($a, $b, $c)
    {
        $result = '';
        // 确保输入都是个位数
        if (!is_numeric($a) || !is_numeric($b) || !is_numeric($c) ||
            $a < 0 || $a > 9 || $b < 0 || $b > 9 || $c < 0 || $c > 9) {
            return ''; // 或者抛出异常
        }

        if (($a == $b && $a != $c) ||
            ($a == $c && $a != $b) ||
            ($b == $c && $b != $a)) {
            $result = GameplayRuleEnum::PAIRS;
        }
        // 判断是否为对子情况
        return $result;
    }

    /**
     * @description: 顺子
     * @param {int} $a
     * @param {int} $b
     * @param {int} $c
     * @return {*}
     */
    public static function isStraight($a, $b, $c)
    {
        $result = '';
        // 确保输入都是个位数（0-9）
        if (!is_numeric($a) || !is_numeric($b) || !is_numeric($c) ||
            $a < 0 || $a > 9 || $b < 0 || $b > 9 || $c < 0 || $c > 9) {
            return '';
        }

        // 去重（顺子必须三个不同数字）
        if ($a == $b || $a == $c || $b == $c) {
            return '';
        }

        // 检查是否是完全升序或完全降序的连续数字
        $numbers = [$a, $b, $c];
        sort($numbers); // 排序后检查是否是 x, x+1, x+2
        list($x, $y, $z) = $numbers;

        // 情况1：升序连续（1,2,3）
        $isAscending = ($x + 1 == $y) && ($y + 1 == $z);

        // 情况2：降序连续（3,2,1）
        $isDescending = ($z + 1 == $y) && ($y + 1 == $x);
        if ($isAscending || $isDescending) {
            $result = GameplayRuleEnum::STRAIGHT;
        }
        return $result;
    }

    /**
     * 获取数字的尾数
     * @param int $number 输入数字
     * @return int 尾数
     */
    public static function getLastDigit($number)
    {
        // 确保输入是整数
        $number = (int)$number;
        // 取绝对值，处理负数情况
        $number = abs($number);
        // 取模10得到尾数
        return $number % 10;
    }


    /**
     * @description: 尾大小
     * @param {*} $number
     * @return {*}
     */
    public static function calculateOneSize($number)
    {
        if ($number >= GameplayRuleEnum::ONE_BIG) {
            return GameplayRuleEnum::BIG;
        }

        if ($number <= GameplayRuleEnum::ONE_SMALL) {
            return GameplayRuleEnum::SMALL;
        }
    }

    /**
     * @description: 获取段位
     * @param {*} $number
     * @return {*}
     */
    public static function getSection($number)
    {
        $result = '';
        if ($number >= GameplayRuleEnum::SECTION_1[0] && $number <= GameplayRuleEnum::SECTION_1[1]) {
            $result = GameplayRuleEnum::ONE;
        } elseif ($number >= GameplayRuleEnum::SECTION_2[0] && $number <= GameplayRuleEnum::SECTION_2[1]) {
            $result = GameplayRuleEnum::TWO;
        } elseif ($number >= GameplayRuleEnum::SECTION_3[0] && $number <= GameplayRuleEnum::SECTION_3[1]) {
            $result = GameplayRuleEnum::THREE;
        } elseif ($number >= GameplayRuleEnum::SECTION_4[0] && $number <= GameplayRuleEnum::SECTION_4[1]) {
            $result = GameplayRuleEnum::FOUR;
        }
        return $result; // 不在任何段中
    }

    /**
     * @description: 近期开奖记录
     * @return {*}
     */
    public static function currentLotteryResults($memberId)
    {
        // $result = self::model()::where('status', self::model()::STATUS_DRAW)->orderBy('id','desc')->take(16)->get();
        // $text = "📅 近期开奖记录\n";
        // $text .= "====================\n";
        // if($result){
        //     foreach($result as $k => $v){


        //         $winArr = explode(',',$v->winning_numbers);

        //         // 组合
        //         $sum = array_sum($winArr);

        //         $combo = [];

        //         $sumOddEven = self::calculateOddEven($sum); // 总和单双

        //         $sumSize = self::calculateSumSize($sum);  // 总和大小

        //         $sumExtremeSize = self::calculateSumExtremeSize($sum);  // 总和极值
        //         if(empty($sumExtremeSize)){
        //             $sumExtremeSize = "-";
        //         }


        //         $tail = self::getLastDigit($sum); // 总和尾数
        //         if($tail == 0){
        //             $tail = '-'; // 尾数
        //         }else{
        //             $tail = '尾'.$tail; // 尾数
        //         }


        //         $text .= "回合：{$v->issue_no}期 \n";
        //         $text .= "结果：".implode('+',explode(',',$v->winning_numbers))."=".array_sum(explode(',',$v->winning_numbers))." \n";
        //         $text .= "组合：{$sumSize} {$sumOddEven} \n";
        //         $text .= "极值：{$sumExtremeSize} \n";
        //         $text .= "尾数：{$tail} \n";
        //         $text .= "---------------------------\n";
        //     }
        //     self::telegram()->sendMessage([
        //             'chat_id' => $memberId,
        //             'text' => $text,
        //         ]);
        // }else{
        //     self::telegram()->sendMessage([
        //             'chat_id' => $memberId,
        //             'text' => "暂无开奖记录",
        //         ]);
        // }
        $result = self::model()::where('status', self::model()::STATUS_DRAW)->orderBy('id', 'desc')->first();
        if ($result) {
            if ($result->image) {
                // self::telegram()->sendPhoto([
                //     'chat_id' => $memberId,
                //     'photo' => InputFile::create(url($result->image)),
                // ]);
                return [
                    'chat_id' => $memberId,
                    'photo' => InputFile::create(url($result->image)),
                ];
            } else {
                // if($result->combo){
                //     self::telegram()->sendMessage([
                //         'chat_id' => $memberId,
                //         'text' => "",
                //     ]);
                // }else{
                // self::telegram()->sendMessage([
                //     'chat_id' => $memberId,
                //     'text' => lang("暂无开奖记录"),
                // ]);
                // }
                return
                    [
                        'chat_id' => $memberId,
                        'text' => lang("暂无开奖记录"),
                    ];

            }

        }
    }

    public static function getCombo($winArr)
    {
        // 组合
        $sum = array_sum($winArr);
        $combo = [];

        $sumSize = self::calculateSumSize($sum);  // 总和大小
        $combo[] = $sumSize;

        $sumOddEven = self::calculateOddEven($sum); // 总和单双
        $combo[] = $sumOddEven;

        $sumExtremeSize = self::calculateSumExtremeSize($sum);  // 总和极值
        if ($sumExtremeSize) {
            $combo[] = $sumExtremeSize;
        }

        $sumBaoZi = self::isBaoZi($winArr[0], $winArr[1], $winArr[2]); // 豹子
        if ($sumBaoZi) {
            $combo[] = $sumBaoZi;
        }
        $sumPair = self::isPair($winArr[0], $winArr[1], $winArr[2]); // 对子
        if ($sumPair) {
            $combo[] = $sumPair;
        }

        $sumStraight = self::isStraight($winArr[0], $winArr[1], $winArr[2]); // 顺子
        if ($sumStraight) {
            $combo[] = $sumStraight;
        }

        $tail = self::getLastDigit($sum); // 总和尾数
        if ($tail == 0 || $tail == 9) {

        } else {
            $combo[] = '尾' . $tail; // 尾数
        }
        return implode(' ', $combo);

    }

    private static function getPlayNowConfig($key, $default)
    {
        $value = config('services.playnow.' . $key, $default);
        return $value === null || $value === '' ? $default : $value;
    }

    private static function getPlayNowProxyUrl(): string
    {
        $scheme = self::getPlayNowConfig('proxy.scheme', 'http');
        $host = self::getPlayNowConfig('proxy.host', '155.138.141.119');
        $port = self::getPlayNowConfig('proxy.port', '3128');
        $username = (string)self::getPlayNowConfig('proxy.username', 'proxyuser');
        $password = (string)self::getPlayNowConfig('proxy.password', '');

        if ($username === '' && $password === '') {
            return "{$scheme}://{$host}:{$port}";
        }

        if ($password === '') {
            return "{$scheme}://" . rawurlencode($username) . "@{$host}:{$port}";
        }

        return "{$scheme}://" . rawurlencode($username) . ':' . rawurlencode($password) . "@{$host}:{$port}";
    }

    private static function getPlayNowProxyLogContext(): array
    {
        $host = self::getPlayNowConfig('proxy.host', '155.138.141.119');
        $password = self::getPlayNowConfig('proxy.password', '');

        return [
            'proxy' => $host === '' ? 'missing' : 'configured',
            'proxy_auth' => $password === '' ? 'missing' : 'configured',
        ];
    }

    private static function fetchPlayNowKenoResult()
    {
        $url = self::getPlayNowConfig('keno_url', self::PLAYNOW_KENO_URL);
        $response = Http::timeout(25)
            ->connectTimeout(10)
            ->withHeaders([
                'Accept' => 'application/json, text/plain, */*',
                'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36',
            ])
            ->withOptions([
                'proxy' => self::getPlayNowProxyUrl(),
                'curl' => [
                    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
                ],
            ])
            ->get($url);

        Log::channel('issue')->info('PlayNow接口响应', [
            'status' => $response->status(),
            'url' => $url,
        ] + self::getPlayNowProxyLogContext());

        if (!$response->successful()) {
            Log::channel('issue')->info('PlayNow接口请求失败', [
                'status' => $response->status(),
                'body' => substr($response->body(), 0, 500),
            ]);
            return null;
        }

        return $response->json();
    }

    private static function convertPlayNowDrawDateToBeijing($drawDate)
    {
        if (empty($drawDate)) {
            return null;
        }

        $sourceTimezone = new \DateTimeZone(self::getPlayNowConfig('timezone', self::PLAYNOW_SOURCE_TIMEZONE));
        $beijingTimezone = new \DateTimeZone(self::BEIJING_TIMEZONE);

        return (new \DateTimeImmutable($drawDate, $sourceTimezone))->setTimezone($beijingTimezone);
    }

    private static function getPlayNowDrawIntervalSeconds(array $result): int
    {
        $timeSinceDraw = isset($result['timeSinceDraw']) ? (int)$result['timeSinceDraw'] : 0;
        $nextDrawSeconds = isset($result['nextKenoDrawTime']) ? (int)$result['nextKenoDrawTime'] : 0;

        if ($timeSinceDraw > 0 && $nextDrawSeconds > 0) {
            return (int)round($timeSinceDraw / 1000) + $nextDrawSeconds;
        }

        return $nextDrawSeconds > 0 ? $nextDrawSeconds : self::PLAYNOW_DRAW_INTERVAL_SECONDS;
    }

    private static function calculatePlayNowPc28(array $numbers): array
    {
        $sortedNumbers = array_map('intval', $numbers);
        sort($sortedNumbers, SORT_NUMERIC);

        $sumByIndexes = function (array $indexes) use ($sortedNumbers) {
            $total = 0;
            foreach ($indexes as $index) {
                $total += $sortedNumbers[$index] ?? 0;
            }
            return $total % 10;
        };

        $firstNumber = $sumByIndexes([1, 4, 7, 10, 13, 16]);
        $secondNumber = $sumByIndexes([2, 5, 8, 11, 14, 17]);
        $thirdNumber = $sumByIndexes([3, 6, 9, 12, 15, 18]);

        return [
            'numbers' => [$firstNumber, $secondNumber, $thirdNumber],
            'total' => $firstNumber + $secondNumber + $thirdNumber,
            'sorted_numbers' => $sortedNumbers,
        ];
    }

    private static function normalizePlayNowNumbers(array $numbers)
    {
        if (count($numbers) !== 20) {
            return null;
        }

        $normalized = [];
        foreach ($numbers as $number) {
            $value = filter_var($number, FILTER_VALIDATE_INT, [
                'options' => [
                    'min_range' => 1,
                    'max_range' => 80,
                ],
            ]);
            if ($value === false) {
                return null;
            }
            $normalized[] = (int)$value;
        }

        if (count(array_unique($normalized)) !== 20) {
            return null;
        }

        return $normalized;
    }

    private static function encodePlayNowKeno($numbers)
    {
        if (!is_array($numbers)) {
            return null;
        }

        $numbers = self::normalizePlayNowNumbers($numbers);
        return $numbers ? json_encode(array_values($numbers)) : null;
    }

    private static function isSamePlayNowIssueNo($issueNo, int $drawNo): bool
    {
        $issueNo = trim((string)$issueNo);
        return ctype_digit($issueNo) && (int)$issueNo === $drawNo;
    }

    private static function normalizePlayNowDrawNo($drawNo)
    {
        $value = filter_var($drawNo, FILTER_VALIDATE_INT, [
            'options' => [
                'min_range' => 1,
            ],
        ]);

        return $value === false ? null : (int)$value;
    }

    private static function parsePlayNowDrawInfo(array $result)
    {
        if (!isset($result['draw']) || empty($result['num']) || !is_array($result['num'])) {
            return null;
        }

        $drawNo = self::normalizePlayNowDrawNo($result['draw']);
        if (!$drawNo) {
            return null;
        }

        $sourceNumbers = self::normalizePlayNowNumbers($result['num']);
        if (!$sourceNumbers) {
            return null;
        }

        try {
            $drawDateBeijing = self::convertPlayNowDrawDateToBeijing($result['drawDate'] ?? null);
        } catch (\Throwable $exception) {
            Log::channel('issue')->info('PlayNow开奖时间解析失败', [
                'draw_date_raw' => $result['drawDate'] ?? '',
                'error' => $exception->getMessage(),
            ]);
            return null;
        }

        $nowBeijing = new \DateTimeImmutable('now', new \DateTimeZone(self::BEIJING_TIMEZONE));
        $drawIntervalSeconds = self::getPlayNowDrawIntervalSeconds($result);
        $startDateTime = $drawDateBeijing ?: $nowBeijing;
        $endDateTime = $startDateTime->modify('+' . $drawIntervalSeconds . ' seconds');
        $pc28 = self::calculatePlayNowPc28($sourceNumbers);

        return [
            'draw_no' => $drawNo,
            'draw_date_raw' => $result['drawDate'] ?? '',
            'draw_date_beijing' => $drawDateBeijing ? $drawDateBeijing->format('Y-m-d H:i:s') : '',
            'next_draw_seconds' => $result['nextKenoDrawTime'] ?? '',
            'draw_interval_seconds' => $drawIntervalSeconds,
            'source_numbers' => $sourceNumbers,
            'sorted_numbers' => $pc28['sorted_numbers'],
            'pc28' => $pc28,
            'winning_numbers' => implode(',', $pc28['numbers']),
            'combo' => self::getCombo($pc28['numbers']),
            'start_time' => $startDateTime->format('Y-m-d H:i:s'),
            'end_time' => $endDateTime->format('Y-m-d H:i:s'),
        ];
    }

    public static function getPlayNowWinningInfo($issueNo): array
    {
        Log::channel('issue')->info('后台手动开奖获取PlayNow数据', [
            'issue_no' => $issueNo,
        ] + self::getPlayNowProxyLogContext());

        try {
            $result = self::fetchPlayNowKenoResult();
        } catch (\Throwable $exception) {
            Log::channel('issue')->info('后台手动开奖获取PlayNow数据异常', [
                'issue_no' => $issueNo,
                'error' => $exception->getMessage(),
                'file' => $exception->getFile(),
                'line' => $exception->getLine(),
            ]);
            return ['code' => self::NOT, 'msg' => '获取开奖信息失败'];
        }

        $drawInfo = self::parsePlayNowDrawInfo($result ?: []);
        if (!$drawInfo) {
            Log::channel('issue')->info('后台手动开奖PlayNow返回数据格式异常', [
                'issue_no' => $issueNo,
                'result' => $result,
            ]);
            return ['code' => self::NOT, 'msg' => '获取开奖信息失败'];
        }

        Log::channel('issue')->info('后台手动开奖PlayNow计算结果', [
            'issue_no' => $issueNo,
            'draw' => $drawInfo['draw_no'],
            'draw_date_raw' => $drawInfo['draw_date_raw'],
            'draw_date_beijing' => $drawInfo['draw_date_beijing'],
            'source_numbers' => implode(',', $drawInfo['source_numbers']),
            'sorted_numbers' => implode(',', $drawInfo['sorted_numbers']),
            'pc28_numbers' => $drawInfo['winning_numbers'],
            'pc28_total' => $drawInfo['pc28']['total'],
            'combo' => $drawInfo['combo'],
        ]);

        if (!self::isSamePlayNowIssueNo($issueNo, $drawInfo['draw_no'])) {
            Log::channel('issue')->info('后台手动开奖期号不匹配', [
                'issue_no' => $issueNo,
                'playnow_draw' => $drawInfo['draw_no'],
            ]);
            return ['code' => self::NOT, 'msg' => '未查询到开奖信息，请手动开奖'];
        }

        return ['code' => self::YES, 'msg' => '获取成功'] + $drawInfo;
    }

// 获取最新的开奖数据
    public static function getLatestIssue()
    {
        Log::channel('issue')->info('开始获取最新期号', [
            'source' => 'playnow',
        ] + self::getPlayNowProxyLogContext());

        try {
            $result = self::fetchPlayNowKenoResult();
        } catch (\Throwable $exception) {
            Log::channel('issue')->info('获取PlayNow最新期号异常', [
                'error' => $exception->getMessage(),
                'file' => $exception->getFile(),
                'line' => $exception->getLine(),
            ]);
            return ['code' => self::NOT, 'msg' => '获取最新期号失败'];
        }

        $drawInfo = self::parsePlayNowDrawInfo($result ?: []);
        if (!$drawInfo) {
            Log::channel('issue')->info('PlayNow返回数据格式异常', [
                'result' => $result,
            ]);
            return ['code' => self::NOT, 'msg' => '获取最新期号失败'];
        }

        $drawNo = $drawInfo['draw_no'];
        $startTime = $drawInfo['start_time'];
        $endTime = $drawInfo['end_time'];
        $pc28 = $drawInfo['pc28'];

        Log::channel('issue')->info('PlayNow最新开奖数据', [
            'draw' => $drawNo,
            'draw_date_raw' => $drawInfo['draw_date_raw'],
            'draw_date_beijing' => $drawInfo['draw_date_beijing'],
            'next_draw_seconds' => $drawInfo['next_draw_seconds'],
            'draw_interval_seconds' => $drawInfo['draw_interval_seconds'],
            'source_numbers' => implode(',', $drawInfo['source_numbers']),
            'sorted_numbers' => implode(',', $drawInfo['sorted_numbers']),
            'pc28_numbers' => $drawInfo['winning_numbers'],
            'pc28_total' => $pc28['total'],
            'next_issue_start_time' => $startTime,
            'next_issue_end_time' => $endTime,
        ]);

        $kenoJson = self::encodePlayNowKeno($drawInfo['source_numbers']);
        if ($kenoJson) {
            $drawIssue = self::findOne(['issue_no' => (string)$drawNo]);
            if ($drawIssue && $drawIssue->status == self::model()::STATUS_DRAW && empty($drawIssue->keno)) {
                $drawIssue->keno = $kenoJson;
                $drawIssue->saveQuietly();

                Log::channel('issue')->info('PlayNow源头号码已补写', [
                    'issue_no' => $drawNo,
                    'source_numbers' => implode(',', $drawInfo['source_numbers']),
                    'status' => $drawIssue->status,
                ]);
            }
        } else {
            Log::channel('issue')->info('PlayNow源头号码跳过写入', [
                'issue_no' => $drawNo,
                'source_numbers_type' => gettype($drawInfo['source_numbers'] ?? null),
            ]);
        }

        $new = true;

        $oldList = self::findAll(['status' => self::model()::STATUS_CLOSE]);    // 获取所有封盘的期号
        foreach ($oldList as $k => $v) {
            if (self::isSamePlayNowIssueNo($v->issue_no, $drawNo)) {
                $winning_numbers = $drawInfo['winning_numbers'];
                $winArr = $pc28['numbers'];


                $combo = $drawInfo['combo'];
                $key = 'lottery_numbers_' . $v->issue_no;
                if (Cache::add($key, $winning_numbers, 100)) {
                    Log::channel('issue')->info('开奖期号: ' . $v->issue_no . ' 开奖号码: ' . $winning_numbers, [
                        'source' => 'playnow',
                        'draw' => $drawNo,
                        'combo' => $combo,
                        'pc28_total' => $pc28['total'],
                    ]);
                    self::lotteryDraw($v->id, $winning_numbers, $combo, '', $drawInfo['source_numbers']);
                    $new = false;
                } else {
                    Log::channel('issue')->info('开奖期号已处理，跳过重复开奖', [
                        'issue_no' => $v->issue_no,
                        'winning_numbers' => $winning_numbers,
                    ]);
                }
                $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
                //更新游戏开关的切换
                if ($pc28Switch == 0) Config::setPc28Switch();
            }
        }

        // sleep(5); // 等待开奖完成

        if ($new) {
            $new_issue_no = $drawNo + 1;  // 新期号

            $newInfo = self::findOne(['issue_no' => $new_issue_no]);   // 找新的期号

            // 不存在
            if (!$newInfo) {
                Log::channel('issue')->info('新增期号: ' . $new_issue_no, [
                    'source' => 'playnow',
                    'start_time' => $startTime,
                    'end_time' => $endTime,
                ]);
                $res = self::submit([
                    'issue_no' => $new_issue_no,
                    'status' => self::model()::STATUS_DRAFT,
                    'start_time' => $startTime,
                    'end_time' => $endTime,
                ]);


                Prediction::prediction($new_issue_no);

                $id = $res['key'] ?? 0;
                if ($id) {
                    self::betting($id); // 开始下注

                }
                Cache::set('new_issue_no', $new_issue_no, 10); // 缓存
            } else {
                Log::channel('issue')->info('期号已存在，无需新增', [
                    'issue_no' => $new_issue_no,
                    'status' => $newInfo->status,
                    'start_time' => $newInfo->start_time,
                    'end_time' => $newInfo->end_time,
                ]);
            }
        }
        
        return $result;
    }

// 获取最新的开奖数据
    public static function getLatestIssue2()
    {
        $url = "https://ydpc28.co/api/pc28/list";
        $result = file_get_contents($url);
        $result = json_decode($result, true);
        if ($result['errorCode'] != 0) {
            return ['code' => self::NOT, 'msg' => '获取最新期号失败'];

        }
        $nextDrawInfo = $result['data']['nextDrawInfo'];
        $startTime = $nextDrawInfo['currentBJTime'];
        // if($nextDrawInfo['nextDrawTime'] >= date('H:i:s')) {
        //     $endTime = date('Y-m-d').' '.$nextDrawInfo['nextDrawTime']; // 下一期的截止时间
        // }else{
        //     $endTime = date('Y-m-d',strtotime('+1 day')).' '.$nextDrawInfo['nextDrawTime']; // 下一期的截止时间
        // }

        $endTime = date('Y-m-d H:i:s', strtotime($startTime) + 210);

        $new = true;

        $list = $result['data']['list'];
        $listKey = [];
        foreach ($list as $k => $v) {
            $listKey[$v['lotNumber']] = $v;
        }

        $oldList = self::findAll(['status' => self::model()::STATUS_CLOSE]);    // 获取所有封盘的期号
        foreach ($oldList as $k => $v) {
            if (isset($listKey[$v->issue_no])) {
                $issue = $listKey[$v->issue_no];
                $winning_numbers = implode(',', str_split((string)$issue['openCode']));

                $winArr = array_map('intval', explode(',', $winning_numbers));

                // 组合
                $sum = array_sum($winArr);

                $combo = [];

                $sumOddEven = self::calculateOddEven($sum); // 总和单双
                $combo[] = $sumOddEven;

                $sumSize = self::calculateSumSize($sum);  // 总和大小
                $combo[] = $sumSize;

                $sumExtremeSize = self::calculateSumExtremeSize($sum);  // 总和极值
                if ($sumExtremeSize) {
                    $combo[] = $sumExtremeSize;
                }


                $sumBaoZi = self::isBaoZi($winArr[0], $winArr[1], $winArr[2]); // 豹子
                if ($sumBaoZi) {
                    $combo[] = $sumBaoZi;
                }
                $sumPair = self::isPair($winArr[0], $winArr[1], $winArr[2]); // 对子
                if ($sumPair) {
                    $combo[] = $sumPair;
                }

                $sumStraight = self::isStraight($winArr[0], $winArr[1], $winArr[2]); // 顺子
                if ($sumStraight) {
                    $combo[] = $sumStraight;
                }

                $tail = self::getLastDigit($sum); // 总和尾数
                if ($tail == 0 || $tail == 9) {

                } else {
                    $combo[] = '尾' . $tail; // 尾数
                }

                $combo = implode(' ', $combo);
                self::lotteryDraw($v->id, $winning_numbers, $combo, '');

            }
        }


        return $result;
    }

// 封盘倒数
    public static function syncCountdownIssue()
    {
        $info = self::model()::where('status', self::model()::STATUS_BETTING)->orderBy('end_time', 'asc')->first();
        if ($info) {
            $now_date = date('Y-m-d H:i:s', time() + IssueService::COUNTDOWN_TO_CLOSING_THE_MARKET);
            if ($info['end_time'] < $now_date) {
                $replyInfo = KeyboardService::findOne(['button' => '封盘倒数']);
                if ($replyInfo) {
                    $text = $replyInfo->reply;
                    $buttons = json_decode($replyInfo->buttons, true);
                    $image = $replyInfo->image;
                    if ($image) {
                        $image = url($image);
                    }
                    if (Cache::has('issue_countdown_' . $info->issue_no)) {

                    } else {
                        $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
                        if ($pc28Switch == 0) {
                            self::asyncBettingGroupNotice($text, $buttons, $image);
                            Cache::put('issue_countdown_' . $info->issue_no, true, 60); // 缓存50秒，防止多次发送
                        }

                    }

                }
            }
        }
    }

// 停止下注
    public static function syncCloseIssue()
    {
        $now_date = date('Y-m-d H:i:s', time() + 30); // 提前30秒
        $list = self::findAll(['status' => self::model()::STATUS_BETTING]);

        foreach ($list as $k => $v) {
            if ($v['end_time'] < $now_date) {
                self::closeBetting($v->id);
            }
        }
    }

// 生成开奖图片
    public static function lotteryImage($issue_no)
    {
        $list = self::model()::where('issue_no', '<=', $issue_no)->where(self::getWhere(['status' => self::model()::STATUS_DRAW]))->orderBy('issue_no', 'desc')->take(20)->get();
        $records = $list->toArray();

        foreach ($records as $k => $v) {
            $winning_numbers = explode(',', $v['winning_numbers']);
            $v['winning_numbers'] = $winning_numbers;

            // 组合
            $sum = array_sum($winning_numbers);

            $v['sum'] = $sum;
            $sumOddEven = self::calculateOddEven($sum); // 总和单双

            $sumSize = self::calculateSumSize($sum);  // 总和大小
            $v['combo'] = $sumSize . ' ' . $sumOddEven;

            $sumExtremeSize = self::calculateSumExtremeSize($sum);  // 总和极值
            if (!$sumExtremeSize) {
                $sumExtremeSize = '-';
            }
            $v['extreme'] = $sumExtremeSize;

            $tail = self::getLastDigit($sum); // 总和尾数
            if ($tail === 0 || $tail === 9) {
                $tailStr = '-';
            } else {
                $tailStr = '尾' . $tail;
            }
            $v['tail'] = $tailStr;

            $records[$k] = $v;
        }
        $service = new LotteryImageService();
        $url = $service->generate($records);

        self::model()::where('issue_no', $issue_no)->update(['image' => $url]);
        return $url;

    }

// 发送开奖图片
    public static function sendLotteryImage($chatId, $issueNo)
    {

        $recordImage = self::lotteryImage($issueNo);
        self::sendMessage($chatId, '', [], url($recordImage));
        // dispatch(new SendTelegramMessageJob('', [], url($recordImage)));
    }
}