<?php

namespace App\Services;

use App\Notification\TelegramBot;
use App\Util;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;

class PaymentWarningService
{
    private const SUCCESS_STATUS = 1;
    private const PAY_METHOD_NAMES = [
        '1' => 'cashapp',
        '2' => 'paypal',
        '4' => 'apple',
        '8' => 'google',
    ];
    private const CONSECUTIVE_FAIL_LIMIT = 20;
    private const RATE_WINDOW_SECONDS = 600;
    private const RATE_THRESHOLD = 30;
    private const RATE_MIN_COUNT = 20;
    private const ALERT_TTL_SECONDS = 600;

    public function checkAll(): int
    {
        $startTime = date('Y-m-d H:i:s', time() - self::RATE_WINDOW_SECONDS);
        $items = DB::connection('read')->table('agent.dbo.order')
            ->select('payment_code', 'order_title')
            ->where('created_at', '>=', $startTime)
            ->whereNotNull('payment_code')
            ->whereNotNull('order_title')
            ->where('payment_code', '<>', '')
            ->where('order_title', '<>', '')
            ->groupBy('payment_code', 'order_title')
            ->lock('with(nolock)')
            ->get();

        $count = 0;
        foreach ($items as $item) {
            $paymentChannel = (string) $item->payment_code;
            $payMethod = (string) $item->order_title;
            if ($this->checkCombination($paymentChannel, $payMethod)) {
                $count++;
            }
        }

        return $count;
    }

    private function checkCombination(string $paymentChannel, string $payMethod): bool
    {
        if ($paymentChannel === '' || $payMethod === '') {
            return false;
        }

        $key = md5($paymentChannel . '|' . $payMethod);
        $this->checkConsecutiveFailedOrders($key, $paymentChannel, $payMethod);
        $this->checkSuccessRate($key, $paymentChannel, $payMethod);

        return true;
    }

    private function checkConsecutiveFailedOrders(string $key, string $paymentChannel, string $payMethod): void
    {
        $orders = DB::connection('read')->table('agent.dbo.order')
            ->select('order_sn', 'user_id', 'pay_status', 'created_at')
            ->where('payment_code', $paymentChannel)
            ->where('order_title', $payMethod)
            ->orderBy('created_at', 'desc')
            ->lock('with(nolock)')
            ->limit(self::CONSECUTIVE_FAIL_LIMIT)
            ->get();

        if ($orders->count() < self::CONSECUTIVE_FAIL_LIMIT) {
            return;
        }

        foreach ($orders as $order) {
            if ((int) ($order->pay_status ?? 0) === self::SUCCESS_STATUS) {
                Redis::del($this->consecutiveAlertKey($key));
                return;
            }
        }

        if (Redis::get($this->consecutiveAlertKey($key))) {
            return;
        }

        Redis::setex($this->consecutiveAlertKey($key), self::ALERT_TTL_SECONDS, 1);
        $latestOrder = $orders->first();
        $message = sprintf(
            "%s 支付连续失败预警\n支付渠道: %s\n支付方式: %s\n支付方式值: %s\n连续未成功订单数: %d\n最新订单: %s\n最新用户ID: %s\n最新创建时间: %s",
            env('APP_ENV'),
            $paymentChannel,
            $this->payMethodName($payMethod),
            $payMethod,
            self::CONSECUTIVE_FAIL_LIMIT,
            $latestOrder->order_sn ?? '',
            $latestOrder->user_id ?? '',
            $latestOrder->created_at ?? ''
        );

        $this->send($message);
    }

    private function checkSuccessRate(string $key, string $paymentChannel, string $payMethod): void
    {
        $windowStart = date('Y-m-d H:i:s', time() - self::RATE_WINDOW_SECONDS);
        $windowEnd = date('Y-m-d H:i:s');
        $stats = DB::connection('read')->table('agent.dbo.order')
            ->selectRaw('count(1) as total, sum(case when pay_status = 1 then 1 else 0 end) as success_total')
            ->where('payment_code', $paymentChannel)
            ->where('order_title', $payMethod)
            ->where('created_at', '>=', $windowStart)
            ->where('created_at', '<=', $windowEnd)
            ->lock('with(nolock)')
            ->first();

        $total = (int) ($stats->total ?? 0);
        if ($total < $this->rateMinCount()) {
            return;
        }

        $successTotal = (int) ($stats->success_total ?? 0);
        $failedTotal = $total - $successTotal;
        $rate = round($successTotal * 100 / $total, 2);
        if ($rate >= self::RATE_THRESHOLD || Redis::get($this->rateAlertKey($key))) {
            return;
        }

        Redis::setex($this->rateAlertKey($key), self::ALERT_TTL_SECONDS, 1);
        $message = sprintf(
            "%s 支付成功率预警\n支付渠道: %s\n支付方式: %s\n支付方式值: %s\n统计窗口: %s ~ %s\n成功率: %s%%\n成功/总数: %d/%d\n失败数: %d",
            env('APP_ENV'),
            $paymentChannel,
            $this->payMethodName($payMethod),
            $payMethod,
            $windowStart,
            $windowEnd,
            $rate,
            $successTotal,
            $total,
            $failedTotal
        );

        $this->send($message);
    }

    private function rateMinCount(): int
    {
        return (int) env('PAYMENT_WARNING_RATE_MIN_COUNT', self::RATE_MIN_COUNT);
    }

    private function payMethodName(string $payMethod): string
    {
        return self::PAY_METHOD_NAMES[$payMethod] ?? $payMethod;
    }

    private function send(string $message): void
    {
        try {
            TelegramBot::getDefault()->sendMsgAndImportant($message);
        } catch (\Throwable $throwable) {
            Util::WriteLog('PaymentWarningService', $throwable->getMessage());
        }
    }

    private function consecutiveAlertKey(string $key): string
    {
        return 'payment_warning:consecutive:' . $key;
    }

    private function rateAlertKey(string $key): string
    {
        return 'payment_warning:success_rate:' . $key;
    }

}
