<?php

namespace App\Services;

use App\Feature;
use App\Jobs\HandlePastBill;
use App\Owner;
use App\OwnerFeeDetail;
use App\OwnerPriceOperation;
use App\OwnerPriceOperationItem;
use App\Services\common\QueryService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use App\Traits\ServiceAppAop;


class OwnerPriceOperationService
{
    use ServiceAppAop;
    protected $modelClass=OwnerPriceOperation::class;
    /**
     * @param Builder $builder
     * @param array $params
     * @return Builder
     */
    private function query(Builder $builder, array $params)
    {
        if ($params["owner_id"] ?? false){
            $ids = $this->ownerGetIds($params["owner_id"]);
            if ($ids)$builder->whereIn("id",$ids);
            unset($params["owner_id"]);
        }
        $columnQueryRules = [
            "name" => ["like"=>""]
        ];
        return app(QueryService::class)->query($params, $builder, $columnQueryRules);
    }

    public function paginate(array $params, array $withs = [])
    {
        return $this->query(OwnerPriceOperation::query()->orderByDesc('id')->with($withs),$params)
            ->paginate($params["paginate"] ?? 50);
    }


    private function ownerGetIds(string $ownerId)
    {
        if (!$ownerId)return [];
        $arr = DB::select(DB::raw("SELECT owner_price_operation_id AS id FROM owner_price_operation_owner WHERE owner_id in (".$ownerId.")"));
        return array_column($arr,"id");
    }

    /**
     * 拷贝目标数据
     *
     * @param object|int $model
     * @param array $values
     * @param array $items
     * @param array|null $owners
     * @param bool $isLoadItem
     *
     * @return object|null
     */
    public function copy($model, $values = [], $owners = null, $items = [], $isLoadItem = true)
    {
        if (is_integer($model))$model = OwnerPriceOperation::query()->find($model);
        if (!$model)return null;
        $values["operation"] = "U";
        $values["target_id"] = $model->id;
        foreach ($model->getFillable() as $column){
            if (!array_key_exists($column,$values))$values[$column] = $model[$column];
        }
        if ($owners === null){
            $query = DB::raw("SELECT * FROM owner_price_operation_owner WHERE owner_price_operation_id = {$model->id}");
            $owners = array_column(DB::select($query),"owner_id");
        }
        /** @var OwnerPriceOperation $copyModel */
        $copyModel = OwnerPriceOperation::query()->create($values);
        $copyModel->owners()->sync($owners);
        $insert = [];
        if ($isLoadItem){
            $model->load("items");
            /** @var \stdClass $model */
            foreach ($model->items as $item){
                $columns = ["strategy","amount","unit_id","unit_price","feature","priority","discount_price","odd_price"];
                if ($items[$item->id] ?? false){
                    foreach ($columns as $column){
                        if (!array_key_exists($column,$items[$item->id]))$items[$item->id][$column] = $item[$column];
                    }
                    $obj = $items[$item->id];
                    unset($items[$item->id]);
                }else{
                    /** @var OwnerPriceOperationItem $item */
                    $obj = $item->toArray();
                    unset($obj["id"]);
                }
                $obj["owner_price_operation_id"] = $copyModel->id;
                $insert[] = $obj;
            }
        }else{
            foreach ($items as $item){
                $arr = [];
                $arr["owner_price_operation_id"] = $copyModel->id;
                $arr["strategy"] = $item["strategy"];
                $arr["amount"] = $item["amount"] ?? null;
                $arr["unit_id"] = $item["unit_id"];
                $arr["unit_price"] = $item["unit_price"];
                $arr["feature"] = $item["feature"] ?? null;
                $arr["odd_price"] = $item["odd_price"] ?? null;
                $arr["priority"] = $item["priority"] ?? 0;
                $arr["discount_price"] = isset($item["discount_price"]) ? (is_array($item["discount_price"]) ? implode(",",$item["discount_price"]) : $item["discount_price"]) : null;
                $insert[] = $arr;
            }
        }
        if ($insert)OwnerPriceOperationItem::query()->insert($insert);
        return $copyModel;
    }

    /**
     * 审核或恢复目标集
     *
     * @param bool $isAudit
     * @param integer|null|array $ownerId
     * @param integer|null|array $ids
     */
    public function auditOrRecover($isAudit = true, $ownerId = null, $ids = null)
    {
        if (!$ownerId && !$ids)return;
        $result = app(QueryService::class)->priceModelAuditOrRecoverQuery($isAudit,OwnerPriceOperation::query(),$ownerId,$ids);
        if ($result["delete"])$this->destroy($result["delete"]);
        if ($result["update"])OwnerPriceOperation::query()->whereIn("id",$result["update"])->update(["operation"=>null,"target_id"=>null]);
        if (!is_array($ownerId))$ownerId = [$ownerId];
        foreach ($ownerId as $ow)Cache::tags("operationFeeOwner:".$ow)->flush();
    }

    public function destroy($id)
    {
        if (!is_array($id))$id = [$id];
        OwnerPriceOperationItem::query()->whereIn("owner_price_operation_id",$id)->delete();

        $query = "IN (";
        for ($i=0;$i<count($id)-1;$i++)$query .= "{$id[$i]},";
        $query .= "{$id[count($id)-1]})";

        $sql = "SELECT * FROM owner_price_operation_owner WHERE owner_price_operation_id {$query}";
        $owners = array_column(DB::select(DB::raw($sql)),"owner_id");
        DB::table("owner_price_operation_owner")->whereIn("owner_price_operation_id",$id)->delete();
        app("OwnerService")->refreshRelevance($owners,1,true);

        return OwnerPriceOperation::destroy($id);
    }

    /**
     * @param array $params
     * @return Model
     */
    public function create(array $params)
    {
        $params["operation"] = "C";
        return OwnerPriceOperation::query()->create($params);
    }

    public function insertItem(array $params)
    {
        OwnerPriceOperationItem::query()->insert($params);
    }

    public function find($id, $withs = [])
    {
        $query = OwnerPriceOperation::query()->with($withs)->find($id);
        return $query;
    }

    public function update(array $params, array $values)
    {
        $query = OwnerPriceOperation::query();
        foreach ($params as $column => $value){
            $query->where($column,$value);
        }
        return $query->update($values);
    }

    public function destroyItem($id)
    {
        return OwnerPriceOperationItem::query()->where("owner_price_operation_id",$id)->delete();
    }

    public function findUpdate(OwnerPriceOperation $model, array $params)
    {
        return $model->update($params);
    }

    /**
     *  获取计费模型缓存
     *
     * @param integer $owner
     * @param string $type
     * @param int|null $typeMark
     *
     * @return array|Collection
     *
     */
    public function getOwnerPriceOperation($owner, $type, $typeMark)
    {
        return Cache::tags("operationFeeOwner:".$owner)->remember("operationFee:".$owner.$type.$typeMark,config("cache.expirations.rarelyChange"),
            function ()use($owner,$type,$typeMark){
                $query = OwnerPriceOperation::query()->with(["items"=>function($query){
                    /** @var Builder $query */
                    $query->orderByRaw("CASE strategy  WHEN '起步' THEN 1 WHEN '默认' THEN 2 WHEN '特征' THEN 3 END DESC,priority");
                }])->where("operation_type",$type)->whereHas("owners",function ($query)use($owner){
                    /** @var Builder $query */
                    $query->where("id",$owner);
                })->where(function(Builder $query){
                    $query->whereNull("operation")->orWhere("operation","");
                })->orderByRaw("strategy desc,priority desc");

                if ($typeMark!==null)$query->where("type_mark",$typeMark);
                else $query->whereNull("type_mark");

                return $query->get();
        });
    }

    /**
     * 获取满减信息
     *
     * @param null|string $discount
     * @param integer $total
     *
     * @return bool|array
     */
    public function getDiscount($discount, $total)
    {
        if ($discount){
            foreach (array_reverse(explode(",",$discount),true) as $index=>$discount){
                if ($total >= $discount)return [$index=>$discount];
            }
        }
        return false;
    }

    /**
     * 处理折扣单
     *
     * @param object $rule
     * @param integer $owner
     * @param bool|array $result
     */
    public function handleDiscount($rule, $owner, $result)
    {
        $sign = false;
        //入口仅在此处存在 缓存1000s
        $key = "owner_price_operation_owner_".$rule->id."_".$owner;
        $discountIndex = key($result);
        $targetValue = $result[$discountIndex];
        $pivot = null;
        try{
            DB::beginTransaction();
            //此处暂时未使用cache的互斥锁 使用sql行锁代替下 防止缓存击穿
            $pivot = app(CacheService::class)->getOrExecute($key,function ()use($key,$targetValue,&$sign,$rule,$owner){
                return DB::selectOne(DB::raw("SELECT * FROM owner_price_operation_owner WHERE owner_price_operation_id = ? AND owner_id = ? for update"),[$rule->id,$owner]);
            },1000);
            if ($pivot && (!$pivot->discount_date || substr($pivot->discount_date,0,7)!=date("Y-m") || $pivot->target_value < $targetValue)){
                //未被标记过处理时间或处理时间不为本月，或上次处理值过期，处理历史即时账单
                $sign = true;
            }
            if ($sign){
                //先标记成功 这将在后续推进历史单处理流程，防止程序在此堵塞
                DB::update(DB::raw("UPDATE owner_price_operation_owner SET discount_date = ?,target_value = ? WHERE  owner_price_operation_id = ? AND owner_id = ?"),
                    [date("Y-m-d"),$targetValue,$rule->id,$owner]);
                $pivot->discount_date = date("Y-m-d");
                $pivot->target_value = $targetValue;
                Cache::put($key,$pivot,1000);
            }
            DB::commit();
        }catch (\Exception $exception){
            DB::rollBack();
            LogService::log(__CLASS__,"即时账单满减处理失败",$exception->getMessage());
        }
        //进入历史单处理
        if ($pivot && $sign)dispatch(new HandlePastBill(array($rule,$owner,$discountIndex,$pivot)));
    }

    /** 参数顺序： 数量 匹配对象 列映射 货主ID 单位ID 类型 SKU .
     *  匹配顺序： 类型 货主 策略 单位 特征                    ..多对多匹配规则废弃，1对1，设单位必定为件,对应规则必然只有一项存在
     *  单位匹配： 件，箱 由小到大，依次换算匹配           .
     *
     *  2:没有总数量存在，都为子项内数量
     *
     * @param array|object|Model $matchObject  key-val
     * @param array $columnMapping       key-val
     * @param string $ownerId
     * @param string $type
     * @param int|null $typeMark
     *
     * @return double|array
     * 错误代码： -1:无匹配对象 -2:无计费模型 -3:未知单位 -4:sku为空 -5:货主未找到 -6:无箱规 -7:未匹配到计费模型
     *
     * 一. 2020-10-10 zzd
     * 二. 2021-01-08 zzd
     * 三. 2021-01-28 zzd
     *      增加满减策略：子策略匹配时不再考虑单，仅件箱换算，满减满足后标记模型修改历史对账单
     *      增加按订单计价策略：主匹配模型增加字段量价，该字段存在时视为按单计价，价格为该值
     * 四. 2021-02-18 zzd
     *      满减多阶段匹配 满减字段由单值改为字符串多值 匹配时转数组寻找最相近
     * 五. 2021-03-18 zzd
     *      区分单据类型，增加字段
     * 六. 2021-03-23 zzd
     *      不严格区分入库出库差异 统一模型
     * 七. 2021-03-30 zzd
     *      增加一级二级特征，零头价，满减按总件,附加费用等
     * 八. 2021-04-19 zzd
     *      增加税率计算，改变返回值数据结构，增加封顶费
     * 九. 2021-04-21 zzd
     *      排除掉order不存在包裹情况，预设输出值为null防止返回0,历史账单处理推进队列防止超时
     */
    public function matching($matchObject, $columnMapping, $ownerId, $type = '出库', $typeMark = null)
    {
        $units = app("UnitService")->getUnitMapping(["件","箱"]); //获取单位映射集
        $rules = $this->getOwnerPriceOperation($ownerId,$type,$typeMark);//货主下的全部规则
        if (!$rules)return -2;  //规则不存在跳出

        //建立一组返回变量
        $id = null;
        $money = null;
        $taxFee = null;

        if ($type == '出库'){
            $total = app("OrderService")->getOrderQuantity($ownerId)+1;//获取该货主本月C端单量
            $matchObject->packages->each(function ($package)use(&$orderTotal){
                $package->commodities->each(function ($commodity)use(&$orderTotal){
                    $orderTotal += (int)$commodity->amount;
                });
            });
            $matchObject[$columnMapping[12]] = $orderTotal;
        }else {
            $total = 0;
            if ($matchObject->storeItems)foreach ($matchObject->storeItems as $item)$total += $item->amount;
            $matchObject[$columnMapping[12]] = $total;
            $total += app("StoreService")->getStoreAmount($ownerId);//获取该货主本月入库件数
        }
        foreach ($rules as $rule){
            if (!$rule->items)continue; //不存在子规则跳出

            $result = $this->getDiscount($rule->discount_count,$total); //满减信息
            if ($result)$this->handleDiscount($rule,$ownerId,$result);//满减存在

            if ($rule->strategy == '特征'){
                if (app("FeatureService")->matchFeature($rule->feature,$columnMapping,$matchObject)){
                    if (!$rule->total_price)$money = $this->matchItem($rule,$columnMapping,$matchObject,$units,$ownerId,$result);
                    $id = $rule->id;
                };
            }else{
                if (!$rule->total_price)$money = $this->matchItem($rule,$columnMapping,$matchObject,$units,$ownerId,$result);
                $id = $rule->id;
            };
            if ($id){
                if ($rule->total_price)$money = $result ? explode(",",$rule->total_discount_price)[key($result)] : $rule->total_price;//按单计价存在，直接返回单总价或减免总价
                $money = $rule->max_fee&&$money>$rule->max_fee ? $rule->max_fee : $money;//封顶费
                if ($money<=0)$money=null;//计算失误
                if ($rule->tax_rate_id && $rule->taxRate)$taxFee = $money*($rule->taxRate->value/100);
                else{
                    /** @var Owner|\stdClass $owner */
                    $owner = Owner::query()->with("taxRate")
                        ->whereHas("taxRate")->find($ownerId);
                    if ($owner)$taxFee = $money*($owner->taxRate->value/100);
                }
                break;
            }
        }
        return array($id,$money,$taxFee);
    }
    /**
     * 根据货主 sku寻找箱规并将指定数量切换为箱 返回箱规
     *
     * @param integer $ownerId
     * @param null|object $commodity
     *
     * @return int
     */
    private function changeUnit($ownerId,$commodity)
    {
        if (!$commodity)return -4;
        if ($commodity["pack_spec"])return $commodity["pack_spec"];
        $pack = app("CommodityService")->getPack($ownerId,$commodity["sku"]);
        if (!$pack)return -6;
        return $pack;
    }

    /**
     * 重置子节点的映射对象  将无限极数组扁平化为二维 以Feature中定义的8:商品数量 key为基准
     *
     * @param object|array $matchObject
     * @param array $columnMapping
     *
     * @return array
     */
    private function resetChildNodeMapping($matchObject,&$columnMapping)
    {
        $need = "";
        foreach (Feature::TYPE_NODE as $index){
            if (!$need)$need=strstr($columnMapping[$index],".",true);
            $columnMapping[$index] = ltrim(strstr($columnMapping[$index],"."),".");
        }
        $nextObj = strstr($columnMapping[8],".",true);
        $first = $matchObject[$need] ?? false;
        if ($first===false)return $matchObject;
        if (!$first)return $first;
        if (is_array($first))$first = reset($first);
        if ($nextObj && is_array($first[$nextObj])){
            $result = [];
            foreach ($matchObject[$need] as $arr)$result = array_merge($result,$arr[$nextObj]);
            return $this->resetChildNodeMapping($result,$columnMapping);
        }
        return $matchObject[$need];
    }

    /**
     * 匹配子策略
     *
     * @param Model|\stdClass $obj 策略对象
     * @param array $columnMapping 映射对象
     * @param Model $matchObject 被匹配对象
     * @param array $units 单位集
     * @param integer $ownerId 货主ID
     * @param bool|array $result 满减信息
     *
     * @return double
     */
    private function matchItem($obj, $columnMapping, $matchObject, $units, $ownerId, $result)
    {
        $matchObject = $this->resetChildNodeMapping($matchObject->toArray(),$columnMapping);
        if (!$matchObject)return -1;
        $total = 0; //商品总数
        foreach ($matchObject as $commodity)$total += $commodity[$columnMapping[8]]; //取对象内商品数量总数将其当作子属性插入原对象
        $surcharge = 0;
        $unitName = "";
        if ($obj->surcharge_unit_id && $obj->surcharge && isset($units[$rule->surcharge_unit_id])){
            if ($units[$obj->surcharge_unit_id] == '件')$surcharge += $obj->surcharge*$total;
            else $surcharge += $obj->surcharge;
        }//耗材附加费
        foreach ($obj->items as $rule){
            if ($result)$rule->unit_price = explode(",",$rule->discount_price)[key($result)]; //满足满减条件，单价调整为满减单价

            if ($rule->strategy=='起步'){
                $startNumber = $rule->amount;
                $money = 0;
                if ($unitName && $startNumber && $unitName != $units[$rule->unit_id])return -3; //校验单位是否一致
                if ($startNumber){
                    $money = $rule->unit_price;
                    $matchObject=$this->settingCount($matchObject,$columnMapping[8],$startNumber);
                }
                if ($matchObject)foreach ($matchObject as $package)if ($package["price"] ?? false)$money += $package[$columnMapping[8]] * $package["price"];
                if (!$startNumber && $money<$rule->unit_price)$money = $rule->unit_price;
                return $money+$surcharge;
            }
            foreach ($matchObject as &$package){
                if ($package["price"] ?? false)continue;
                if (!isset($units[$rule->unit_id]))return -3;
                if (!$unitName)$unitName = $units[$rule->unit_id];
                else if ($unitName != $units[$rule->unit_id]) return -3;
                if ($rule->strategy=='特征'){
                    $package[$columnMapping[10]] = $total; //设置一个不存在的总数进入原对象
                    if (!app("FeatureService")->matchFeature($rule->feature,$columnMapping,$package)) continue;
                }
                $package["price"] = $rule->unit_price;
            }
            if ($units[$rule->unit_id] == '箱'){ //为箱时同步商品寻找箱规
                $amount = 0;
                foreach ($matchObject as $commodity){
                    $pack = $this->changeUnit($ownerId,$commodity[$columnMapping[9]]);
                    if ($rule->odd_price){
                        $amount += floor($amount/$pack);
                        $surcharge += $rule->odd_price * ($amount%$pack); //零头附加费
                    }else$amount += ceil($amount/$pack);
                }
                if ($amount<0)return $amount;
                $package[$columnMapping[8]] = $amount;
            }
        }
        if ($matchObject){
            $money = $surcharge;
            foreach ($matchObject as $package)if ($package["price"] ?? false)$money += $package[$columnMapping[8]] * $package["price"];
        }
        return $money ?? -7;
    }
    //递归式重新设置数量
    private function settingCount($packages,$amountColumn,$startNumber)
    {
        if (!$packages) return null;
        $maxPrice = 0;
        $index = null;
        foreach ($packages as $i => $package){
            if ($package[$amountColumn] <= 0){
                unset($packages[$i]);continue;
            }
            if (!($package["price"] ?? false)){
                $package["price"] = 0;
                $packages[$i]["price"] = 0;
            }
            if ($package["price"] > $maxPrice || ($package["price"]==0 && $maxPrice==0)){
                $maxPrice = $package["price"];
                $index = $i;
            }
        }
        if ($packages[$index][$amountColumn] >= $startNumber){
            $packages[$index][$amountColumn] -= $startNumber;
            return $packages;
        }else{
            $startNumber -= $packages[$index][$amountColumn];
            unset($packages[$index]);
            $this->settingCount($packages,$amountColumn,$startNumber);
        }
    }

    /**
     * 处理历史账单
     *
     * @param object $rule
     * @param int $owner
     * @param int $discountIndex
     * @param object $pivot
     */
    public function handlePastBill($rule, $owner, $discountIndex, $pivot)
    {
        try{
            DB::beginTransaction();
            $month = date("Y-m");
            $day = date("t",strtotime($month));
            $units = app("UnitService")->getUnitMapping(["件","箱"]); //获取单位映射集
            foreach (OwnerFeeDetail::query()->with(["order.logistic","order.shop","order.packages.commodities.commodity","order.batch"])
                         ->where("owner_id",$owner)
                         ->whereBetween("worked_at",[$month."-01",$month."-".$day])->get() as $detail){
                $order = $detail->order;

                $logistic_fee = 0;
                if ($logistic_fee!==null && $logistic_fee<0)$logistic_fee = null;

                $money = $this->matchItem($rule,Feature::MAPPING["order"],$order,$units,$owner,[$discountIndex=>true]);
                if ($money>0)$detail->update(["work_fee"=>$money]);
                else LogService::log(__CLASS__,"处理历史即时账单时发生匹配错误","账单主键：".$detail->id."; 错误代码".$money);
            };
            DB::commit();
        }catch (\Exception $e){
            DB::rollBack();
            //处理失败回退标记
            DB::update(DB::raw("UPDATE owner_price_operation_owner SET discount_date = ?,target_value = ? WHERE  owner_price_operation_id = ? AND owner_id = ?"),
                [$pivot->discount_date,$pivot->target_value,$rule->id,$owner]);
            LogService::log(__CLASS__,"处理历史即时账单时发生系统错误","计费模型主键：".$rule->id."; 错误信息".$e->getMessage());
        }
    }
}
