<?php

namespace tw\async\activities;

use app\admin\model\user\User;
use app\admin\model\user\UserBill;
use app\models\store\StoreOrder;
use app\models\store\StoreOrderCartInfo;
use app\models\system\SystemAwardHistory;
use app\models\user\UserNotice;
use crmeb\basic\BaseModel;
use tw\redis\ActivityPool;

/**
 * 活动基类
 * 
 * 活动设计为：一个活动有一个商品分类，该分类下的商品为该活动商品
 */
abstract class ActivityCalc
{
    protected $ctx = [];

    public function __construct()
    {
    }

    /**
     * 根据分类 ID 来获取该分类下已被购买的单件商品，及其订单，用户信息
     * @param $cate_id
     * @return array
     */
    protected static function getOrders($cate_id)
    {
        $orders = StoreOrderCartInfo::alias('ci')
            ->join('store_order o', 'o.id=ci.oid')
            ->join('store_product p', 'ci.product_id=p.id')
            ->join('user u', 'u.uid = o.uid')
            ->where('o.paid', 1)
            ->where('o.status', '>=', 0)
            ->where('o.refund_status', 0)
            ->where('o.is_del', 0)
            ->where('o.is_system_del', 0)
            ->where('p.cate_id', $cate_id)
            ->where('ci.activity', '')
            ->field('o.id, o.order_id, o.pay_price, ci.product_id, ci.cart_info, u.uid, u.now_money, u.pay_count, p.store_name')
            ->select();
        $orders = $orders ? $orders->toArray() : [];
        return $orders;
    }

    /**
     * 用统一的架构来计算输赢
     */
    public function calc()
    {
        // 活动分类ID
        $cate_id = $this->getId();
        $products = self::getOrders($cate_id);
        $pNum = count($products);
        $minNum = $this->getMinimalProductNum();
        if ($pNum < $minNum) {
            warnlog("not enough order. product num: $pNum, $minNum needed, stop activity:" . $this->getNameCN());
            SystemAwardHistory::create([
                'activity' => $this->getName(),
                'result' => $this->getResult(mt_rand(0, 10) < 5 ? true : false),
                'ts' => time(),
                'order_num' => $pNum,
                'winner_num' => 0,
                'total_paid' => 0,
                'diff_paid' => 0,
                'profit' => 0,
                'rate' => $this->repRate(),
            ]);
            return;
        }

        // 按商品处理订单
        $counter = 0;       // 订单中商品计数器
        $left = [];         // 左侧商品
        $right = [];        // 右侧商品
        $left_spent = 0;    // 左侧总价
        $right_spent = 0;   // 右侧总价
        $left_cost = 0;     // 左侧总成本
        $right_cost = 0;    // 右侧总成本

        // 打乱商品顺序
        shuffle($products);
        shuffle($products);

        // 标记有多件商品的订单, 再次统计到属于某个订单的商品时, 订单会重复
        $orders = [];
        foreach ($products as &$p) {
            $counter += 1;

            $ci = json_decode($p['cart_info'], true);
            $num = $ci['cart_num'];  // 购买个数
            $productInfo = isset($ci['productInfo']) ? $ci['productInfo'] : [];
            $attrInfo = isset($productInfo['attrInfo']) ? $productInfo['attrInfo'] : [];
            // 价格, 成本
            $price = isset($attrInfo['price']) ? $attrInfo['price'] : 0.0;
            $cost = isset($attrInfo['cost']) ? $attrInfo['cost'] : 0.0;
            // 付款总额, 总成本
            $ppaid = bcmul($num, $price, 2); // product paid
            $pcost = bcmul($num, $cost, 2);
            // orderId
            $oid = $p['id'];
            // 标记付款和成本
            $p['paid'] = $ppaid;
            $p['cost'] = $pcost;
            $p['image'] = isset($attrInfo['image']) ? $attrInfo['image'] : '';

            // 处理一个订单多件商品的情况
            if (!isset($orders[$oid])) {
                $orders[$oid] = 1;
            } else {
                $orders[$oid] += 1;
            }

            // if ($ppaid <= 1.0) {
            //     Log::warning("impossible price, order: ". $p['id']);
            //     continue;
            // }
            // if ($pcost <= 0.1) {
            //     Log::warning("impossible cost, order: ". $p['id']);
            //     continue;
            // }

            // 按一个规则, 把商品分为两份, 其中一份中奖
            if ($this->leaningJudge($counter, $p, $attrInfo)) {
                $left[] = $p;
                $left_spent = bcadd($left_spent, $ppaid, 2);
                $left_cost = bcadd($left_cost, $pcost, 2);
            } else {
                $right[] = $p;
                $right_spent = bcadd($right_spent, $ppaid, 2);
                $right_cost = bcadd($right_cost, $pcost, 2);
            }
        } // foreach

        // 利润
        $left_profit = bcsub($left_spent, $left_cost, 2);
        $right_profit = bcsub($right_spent, $right_cost, 2);
        // 定输赢
        $diff_money = abs(bcsub($left_spent, $right_spent, 2));
        // if ($diff_money < 0.0) {
        //     $diff_money = -$diff_money;
        // }
        // 假定左边胜利
        $profit = $right_profit;
        $winners = $left;
        $losers = $right;
        // 左边利润高则右边胜利
        if ($left_profit > $right_profit) {
            $winners = $right;
            $losers = $left;
            $profit = $left_profit;
        }
        // 如果只有 1 件商品
        if ($pNum == 1) {
            // 此时，订单必为 loser
            // 47% 概率胜
            $itWin = false; // 这单胜了 ?
            $dice = mt_rand(0, 100);
            if ($dice < 60) {
                $itWin = true;
            }

            $poolBalance = floatval((new ActivityPool)->get('', $this->getName())); // pool 中金额
            if (!$itWin) {
                // 如果为新用户，且池子有钱，让他胜
                if ($poolBalance > $profit && $products[0]['pay_count'] <= 1) {
                    $itWin = true;
                }
            }

            if ($itWin) {
                $profit = -$profit;
                list($winners, $losers) = [$losers, $winners];  // swap
            }
            (new ActivityPool)->hincr_by_float('', $this->getName(), floatval($profit));

            warnlog($this->getName() . ": single order[product]. dice:$dice, remain:$poolBalance, pay_count:" . $products[0]['pay_count']);
        }

        $result = $this->getResult($winners == $left);

        warnlog('products:' . json_encode($products)
            . ' left:' . json_encode($left) . ' left_spent:' . $left_spent
            . ' left_cost:' . $left_cost . ' left_profit:' . $left_profit
            . ' right' . json_encode($right) . ' right_spent:' . $right_spent
            . ' right_cost:' . $right_cost . ' right_profit:' . $right_profit
            . ' diff_money:' . $diff_money . ' profit:' . $profit . ' result:' . $result);

        // 保存开奖结果
        SystemAwardHistory::create([
            'activity' => $this->getName(),
            'result' => $result,
            'ts' => time(),
            'order_num' => count($left) + count($right),
            'winner_num' => count($winners),
            'total_paid' => $left_spent + $right_spent,
            'diff_paid' => $diff_money,
            'profit' => $profit,
            'rate' => $this->repRate(),
        ]);

        // 结果处置 胜方退款，败方发货
        foreach ($winners as $p) {
            $single = $orders[$p['id']] == 1;
            $this->execute($p, $single, true);
        }
        foreach ($losers as $p) {
            $single = $orders[$p['id']] == 1;
            $this->execute($p, $single, false);
        }

        warnlog("activity" . $this->getNameCN() . " calc finished. result: $result");
    }

    protected function execute($order, $single = true, $win = true)
    {
        BaseModel::beginTrans();
        $result = 0;
        $reparation = 0.0;

        $product_name = $order['store_name'];
        $order_str = $order['order_id'];
        $paid = $order['paid'];
        $cost = $order['cost'];

        if ($win) {
            $result = 1;
            $reparation = bcmul(bcsub($paid, $cost, 2), $this->repRate(), 2);
        }
        if ($reparation < 0.0) {
            warnlog("reparation = $reparation, orderId=" . $order_str);
            $reparation = 0.0;
        }
        // 标记商品为已参与活动
        $r1 = StoreOrderCartInfo::where('oid', $order['id'])
            ->where('product_id', $order['product_id'])
            ->update([
                'activity' => $this->getName(),
                'result' => $result,
                'reparation' => $reparation,
            ]);

        // 没胜利，退出
        if (!$win) {
            BaseModel::checkTrans($r1);
            return;
        }

        $refund_price = bcadd($paid, $reparation, 2);
        // 订单全部商品都中奖的退单
        $r2 = true;
        if ($single) {
            $r2 = StoreOrder::where('id', $order['id'])->update([
                'refund_price' => $refund_price,
                'refund_status' => 2,
            ]);
        }
        // 中奖的 退款并赔款返回到佣金
        $r3 = UserBill::income(
            '活动' . $this->getNameCN() . "[$order_str]",
            $order['uid'],
            'now_money',
            'brokerage',
            $refund_price,
            $order['id'],
            bcadd($order['now_money'], $refund_price, 2),
            '订单退款' . $order['paid'] . '+' . $reparation . '元'
        );
        $r4 = User::bcInc($order['uid'], 'brokerage_price', $refund_price, 'uid');

        $ok = $r1 && $r2 && $r3 && $r4;
        BaseModel::checkTrans($ok);

        if ($ok) {
            // 中奖用户发送中奖消息.
            UserNotice::sendNoticeTo(
                $order['uid'],
                '您的订单已处理 点击查看',
                "您好,由于活动商品库存有限，您的订单 $product_name(订单号 $order_str) 未能成功发货。 该商品付款 $paid 元已如数退还，并赔付您 $reparation 元作为补偿，请在”我的佣金”中核实。对您造成的不便我们深感抱歉。感谢您对我们的信任和喜爱，美天旺祝您生活愉快。",
                $order['image']
            );
        }
    }   // execute

    // 活动所属的 $categoryId
    abstract protected function getId();

    // 活动英文名, 长度 <8
    abstract protected function getName();

    // 活动中文名
    abstract protected function getNameCN();

    // 赔款所占商品利润的百分比
    abstract protected function repRate();

    // 满足开奖的最小订单数
    abstract protected function getMinimalProductNum();

    // 获得开奖结果 int
    abstract protected function getResult($leftwin = true);
    /**
     * 根据此函数返回值来划分 getOrders() 返回的订单为两份, 其中一份中奖, 当本函数返回 true, $product 加入"左边"
     *
     * @param $index: $product 所在序号, 从 1 开始
     * @param $product: getOrders() 返回列表中的单个元素
     * @param $attr: 商品属性
     * @return boolean true 加入左边
     */
    abstract protected function leaningJudge($index, $product, $attr);
}
