<?php

namespace App\Http\Controllers\Goods;

use App\Events\BatchStockUpdateEvent;
use App\Events\StockUpdateEvent;
use App\Exports\GoodsSkusExport;
use App\Exports\WeekDataExport;
use App\Http\Controllers\Controller;
use App\Http\Requests\GoodsRequest;
use App\Http\Requests\GoodsSkuRequest;
use App\Imports\InventoryImport;
use App\Imports\NewSetImport;
use App\Models\BusinessOrderItem;
use App\Models\DailySalesReport;
use App\Models\DeveloperConfig;
use App\Models\Goods;
use App\Models\Log;
use App\Models\Log as LogModel;
use App\Utils\ArrayUtils;
use App\Utils\DateTimeUtils;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Models\GoodsSku;
use App\Http\Resources\GoodsSkuResource;
use App\Imports\GoodsSkusImport;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Facades\Excel;
use App\Models\DailyStockRecord;
use App\Models\Shop;

class GoodsSkusController extends Controller
{
    public function __construct(Request $request)
    {
        $this->log = new LogModel([
            'module' => 'goods',
            'action' => $request->getMethod(),
            'target_type' => 'goods_sku',
        ]);
    }

    public function index(Request $request)
    {
        $fields = implode(',', [
            'shop_id',
            'external_sku_id',
            'SUM(goods_number) - SUM(already_cancel_number) as number',
        ]);
        $orderRestTime = DeveloperConfig::query()
            ->where('key', DeveloperConfig::$ORDER_RESET_TIME)
            ->value('value');
        if (is_null($orderRestTime)) {
            $orderRestTime = date('Y-m-d 07:00:00');
        }
        $businessOrderItems = BusinessOrderItem::query()
            ->select(DB::raw($fields))
            ->with([
                'shop:id,name',
                'goodsSku:id,external_sku_id'
            ])
            ->where('created_at', '>', $orderRestTime)
            ->where('external_sku_id', '<>', '')
            ->groupBy(['shop_id', 'external_sku_id'])
            ->orderByDesc('number')
            ->get()
            ->toArray();
        $ids = $externals = [];
        foreach ($businessOrderItems as $businessOrderItem) {
            if (is_null($businessOrderItem['goods_sku'])) {
                continue;
            }
            $id = $businessOrderItem['goods_sku']['id'];
            if (isset($ids[$id])) {
                $ids[$id] += (int)$businessOrderItem['number'];
            } else {
                $ids[$id] = (int)$businessOrderItem['number'];
            }

            $externals[$businessOrderItem['external_sku_id']][] = $businessOrderItem;
        }
        arsort($ids);

        $builder = GoodsSku::query();
        $this->preparQueryGoodsSkus($request, $builder);
        $day = DateTimeUtils::getToday();
        $goodsSkus = (clone $builder)->filter()
            ->where('is_combination', 0)
            ->orderByDesc('stock')
            ->pluck('stock', 'id')
            ->toArray();
        $finalIds = [];
        foreach ($ids as $id => $number) {
            if (isset($goodsSkus[$id])) {
                $finalIds[] = $id;
                unset($goodsSkus[$id]);
            }
        }
        $finalIds = array_merge($finalIds, array_keys($goodsSkus));
        $idField = implode(',', $finalIds);

        $goodsSkus = (clone $builder)->with(['goods' => function ($query) {
            $query->with(['type:id,name', 'brand:id,name']);
        }])
            ->with(['daily' => function ($query) use ($day) {
                $query->where('day', $day);
            }])
            ->whereIn('id', $finalIds)
            ->orderByRaw("FIELD(id, {$idField})")
            ->paginate($request->get('per_page'));

        $rolesName = $request->user()->getRoleNames()->toArray();
        foreach ($goodsSkus as &$sku) {
            $lastInventoryTime = $sku['daily']['inventory_time'] ?: date('Y-m-d 07:00:00');
            if (isset($externals[$sku['external_sku_id']])) {
                $sku['order_detail'] = $externals[$sku['external_sku_id']];
                $sku['order_goods_num'] = array_sum(array_column($externals[$sku['external_sku_id']], 'number'));
            } else {
                $sku['order_detail'] = [];
                $sku['order_goods_num'] = 0;
            }
            $sku['order_goods_num'] -= $sku['daily']['reissue_num'];
            $sku['inventory_time'] = $lastInventoryTime;
            if ('销售' === $rolesName[0]) {
                $sku['cost'] = 0;
            }
        }

        return GoodsSkuResource::collection($goodsSkus);
    }

    private function preparQueryGoodsSkus(Request $request, &$builder)
    {
        if ($request->get('keyword_type') && $request->get('keyword_value')) {
            $skuIds = Log::query()->where('target_type', 'goods_sku')
                ->where('target_field', $request->keyword_type)
                ->whereBetween('created_at', explode(' - ', $request->keyword_value))
                ->pluck('target_id')
                ->toArray();
            $builder->whereIn('id', $skuIds);
        }
        if ($request->get('type_id') || $request->get('brand_id')) {
            $goodsIds = Goods::query()->filter()->pluck('id')->toArray();
            $builder->whereIn('goods_id', $goodsIds);
        }
        if ($request->get('goods_title')) {
            $builder->where('name', 'like', '%' . $request->goods_title . '%');
        }
    }

    public function show($id)
    {
        $sku = GoodsSku::query()
            ->with(['goods' => function ($query) {
                $query->with(['type:id,name', 'brand:id,name']);
            }])
            ->find($id)
            ->toArray();
        $shops = Shop::query()->get(['id', 'name'])->toArray();
        if (empty($sku['thumb_url'])) {
            foreach ($shops as $shop) {
                $sku['thumb_url'][] = [
                    'shop_id' => $shop['id'],
                    'shop_name' => $shop['name'],
                    'img_url' => '',
                ];
            }
        }

        return ['data' => $sku];
    }

    public function update($id, Request $request)
    {
        $goodsRules = (new GoodsRequest())->arrayRules('goods.');
        $skuRules = (new GoodsSkuRequest())->arrayRules('sku.');
        $validator = Validator::make($request->all(), array_merge($goodsRules, $skuRules));
        if ($validator->fails()) {
            $this->setValidatorFailResponse($validator->getMessageBag()->getMessages());

            return response($this->res, $this->res['httpCode']);
        }
        DB::beginTransaction();
        try {
            // 商品规格更新
            $sku = GoodsSku::query()->find($id);
            $this->setBeforeUpdateForLog($sku->toArray());
            $skuInfo = $request->sku;
            $skuInfo['external_sku_id'] = $request->goods['goods_code'] . '_' . $request->sku['sku_code'];
            $skuInfo['name'] = $request->goods['title'] . $request->sku['title'];
            $sku->update($skuInfo);
            $this->setAfterUpdateForLog($sku->toArray());
            $this->addLog($id, 'update');
            // 商品更新
            $goods = Goods::query()->find($sku->goods_id);
            $this->log = new LogModel([
                'module' => 'goods',
                'action' => $request->getMethod(),
                'target_type' => 'goods',
            ]);
            $this->setBeforeUpdateForLog($goods->toArray());
            $goods->update($request->goods);
            $this->setAfterUpdateForLog($goods->toArray());
            $this->addLog($sku->goods_id, 'update');
            DB::commit();
        } catch (\Exception $exception) {
            DB::rollBack();
            $this->res = [
                'httpCode' => 400,
                'errorCode' => 400416,
                'errorMessage' => $exception->getMessage(),
            ];
        }

        return response($this->res, $this->res['httpCode']);
    }

    public function batchUpdate(Request $request)
    {
        $appendRules = [
            'updateType' => ['required', 'string', Rule::in(['newest', 'inventory', 'stock'])],
            'skus' => ['required', 'array'],
            'skus.*.id' => [
                'required',
                Rule::exists('goods_skus', 'id'),
            ],
        ];
        $skuRules = (new GoodsSkuRequest())->arrayRules('skus.*.');
        $validator = Validator::make($request->all(), array_merge($appendRules, $skuRules));
        if ($validator->fails()) {
            $this->setValidatorFailResponse($validator->getMessageBag()->getMessages());

            return response($this->res, $this->res['httpCode']);
        }
        $function = $request->updateType;

        return $this->$function($request);
    }

    /**
     * 上新
     *
     * @param $request
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
     */
    private function newest($request)
    {
        DB::beginTransaction();
        try {
            $logs = [];
            foreach ($request->skus as $sku) {
                $costLog = $arrivedLog = [
                    'module' => 'goods',
                    'action' => $request->getMethod(),
                    'target_type' => 'goods_sku',
                    'target_id' => $sku['id'],
                    'user_id' => $request->user()->id
                ];
                // 成本
                $goodsSku = GoodsSku::query()->where('id', $sku['id'])->first(['id', 'cost', 'stock', 'num']);
                $costLog['target_field'] = 'cost';
                $costLog['before_update'] = $goodsSku->cost;
                $goodsSku->cost = $sku['cost'];
                $goodsSku->reference_price = $sku['cost'] * 1.5;
                $goodsSku->stock += $sku['arrived_today_num'];
                $goodsSku->num += $sku['arrived_today_num'];
                $goodsSku->save();
                $costLog['after_update'] = $goodsSku->cost;
                $logs[] = $costLog;
                // 今日到货
                $record = DailyStockRecord::query()->where('sku_id', $sku['id'])->where('day', DateTimeUtils::getToday())->first(['id', 'arrived_today_num']);
                $arrivedLog['target_field'] = 'arrived_today_num';
                $arrivedLog['before_update'] = $record->arrived_today_num;
                $record->arrived_today_num += $sku['arrived_today_num'];
                $record->save();
                $arrivedLog['after_update'] = $record->arrived_today_num;
                $logs[] = $arrivedLog;
            }
            $log = new LogModel();
            $log->batchInsert($logs);
            DB::commit();
            // 批量更新
            event(new BatchStockUpdateEvent(array_column($request->skus, 'id')));
        } catch (\Exception $exception) {
            DB::rollBack();
            $this->res = [
                'httpCode' => 400,
                'errorCode' => 400500,
                'errorMessage' => $exception->getMessage(),
            ];
        }

        return response($this->res, $this->res['httpCode']);
    }

    /**
     * 库存盘点
     *
     * @param $request
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
     */
    private function inventory($request)
    {
        DB::beginTransaction();
        try {
            $logs = [];
            $requestSkus = ArrayUtils::index($request->skus, 'id');
            $skus = GoodsSku::query()
                ->whereIn('id', array_keys($requestSkus))
                ->with(['goods:id,goods_code'])
                ->get(['id', 'goods_id', 'sku_code'])
                ->toArray();
            $today = DateTimeUtils::getToday();
            $nextDay = DateTimeUtils::getNextDay();
            $dateTime = date('Y-m-d H:i:s');
            foreach ($skus as $sku) {
                if (!isset($requestSkus[$sku['id']])) {
                    continue;
                }
                // 更新今天
                $record = DailyStockRecord::query()->where('sku_id', $sku['id'])->where('day', $today)->first();
                // 日志记录
                $inventoryLog = [
                    'module' => 'goods',
                    'action' => $request->getMethod(),
                    'target_type' => 'goods_sku',
                    'target_id' => $sku['id'],
                    'user_id' => $request->user()->id,
                    'target_field' => 'inventory',
                    'before_update' => $record->inventory,
                    'after_update' => $requestSkus[$sku['id']]['inventory']
                ];
                $externalSkuId = $sku['goods']['goods_code'] . '_' . $sku['sku_code'];
                // 自上一次盘点过后有订单发生的数量(包含退单)
                $lastInventoryTime = $record->inventory_time;
                $addOrderGoodsNum = BusinessOrderItem::query()
                    ->where('external_sku_id', $externalSkuId)
                    ->when($lastInventoryTime, function ($query) use ($lastInventoryTime) {
                        $query->where('updated_at', '>', $lastInventoryTime);
                    })
                    ->sum('goods_number');
                $reduceOrderGoodsNum = BusinessOrderItem::query()
                    ->where('external_sku_id', $externalSkuId)
                    ->when($lastInventoryTime, function ($query) use ($lastInventoryTime) {
                        $query->where('updated_at', '>', $lastInventoryTime);
                    })
                    ->sum('already_cancel_number');
                $orderGoodsNum = $addOrderGoodsNum - $reduceOrderGoodsNum;
                $record->inventory = $requestSkus[$sku['id']]['inventory'];
                $record->inventory_time = $dateTime;
                $record->order_goods_num += $orderGoodsNum;
                $record->save();
                $inventoryLog['message'] = '盘点时订单商品数量: ' . $orderGoodsNum;
                $logs[] = $inventoryLog;
                // 更新明天
                DailyStockRecord::updateOrCreate(
                    ['sku_id' => $sku['id'], 'day' => $nextDay],
                    [
                        'inventory' => $record->inventory,
                        'inventory_time' => $dateTime,
                    ]
                );
            }
            $log = new LogModel();
            $log->batchInsert($logs);
            DB::commit();
        } catch (\Exception $exception) {
            DB::rollBack();
            $this->res = [
                'httpCode' => 400,
                'errorCode' => 400500,
                'errorMessage' => $exception->getMessage(),
            ];
        }

        return response($this->res, $this->res['httpCode']);
    }

    /**
     * 库存修改
     *
     * @param $request
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
     */
    private function stock($request)
    {
        $skus = $request->skus;
        $update = reset($skus);
        DB::beginTransaction();
        try {
            $sku = GoodsSku::query()->where('id', $update['id'])->with(['goods:id,goods_code'])->first();
            $record = DailyStockRecord::query()
                ->where('sku_id', $sku->id)
                ->where('day', DateTimeUtils::getToday())
                ->first();
            $this->setBeforeUpdateForLog([
                'two_days_ago_num' => $sku->two_days_ago_num,
                'yesterday_num' => $sku->yesterday_num,
                'arrived_today_num' => $record->arrived_today_num,
                'num' => $sku->num,
                'stock' => $sku->stock,
            ]);
            $num = $update['two_days_ago_num'] + $update['yesterday_num'] + $update['arrived_today_num'];
            $sku->num = $num;
            $sku->stock += ($num - $record->arrived_today_num - $sku->two_days_ago_num - $sku->yesterday_num);
            $sku->two_days_ago_num = $update['two_days_ago_num'];
            $sku->yesterday_num = $update['yesterday_num'];
            $sku->save();
            $record->arrived_today_num = $update['arrived_today_num'];
            $record->save();
            $this->setAfterUpdateForLog([
                'two_days_ago_num' => $sku->two_days_ago_num,
                'yesterday_num' => $sku->yesterday_num,
                'arrived_today_num' => $record->arrived_today_num,
                'num' => $sku->num,
                'stock' => $sku->stock,
            ]);
            $this->addLog($sku->id, 'stock');
            DB::commit();
            event(new StockUpdateEvent($sku));
        } catch (\Exception $exception) {
            DB::rollBack();
            $this->res = [
                'httpCode' => 400,
                'errorCode' => 400416,
                'errorMessage' => $exception->getMessage(),
            ];
        }

        return response($this->res, $this->res['httpCode']);
    }

    public function updateField($id, Request $request)
    {
        $rules = [
            'updateField' => [
                'required',
                Rule::in(['reference_price', 'reserve', 'loss_num', 'status', 'goal_rate'])
            ],
            'reference_price' => [
                'sometimes',
                'numeric',
                'gt:0'
            ],
            'reserve' => [
                'sometimes',
                'integer',
            ],
            'loss_num' => [
                'sometimes',
                'integer',
            ],
            'reason' => [
                'sometimes',
                'required',
                'string'
            ],
            'status' => [
                'sometimes',
                'required',
                'integer',
                Rule::in([0, 1, 2])
            ],
            'goal_rate' => [
                'sometimes',
                'numeric',
            ],
        ];
        $validator = Validator::make($request->all(), $rules);
        if ($validator->fails()) {
            $this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
            goto end;
        }
        $updateField = \request('updateField');
        $sku = GoodsSku::query()->find($id);
        if ('loss_num' === $updateField) {
            $record = DailyStockRecord::query()
                ->where('sku_id', $id)
                ->where('day', DateTimeUtils::getToday())
                ->first(['id', 'loss_num']);
            $this->log->message = $request->get('reason');
            $this->setBeforeUpdateForLog($record->loss_num);
            $record->loss_num += $request->loss_num;
            $record->save();
            $this->setAfterUpdateForLog($record->loss_num);
            $sku->stock -= $request->loss_num;
            $sku->save();
        } else {
            $this->setBeforeUpdateForLog($sku->$updateField);
            if ('reserve' === $updateField) {
                $changeNum = $sku->reserve - $request->reserve;
                if (0 > $changeNum + $sku->stock) {
                    $this->setValidatorFailResponse('预留量超过库存数量');
                    goto end;
                }
                $sku->stock += $changeNum;
            }
            $sku->$updateField = $request->$updateField;
            $sku->save();
            $this->setAfterUpdateForLog($sku->$updateField);
        }
        if (in_array($updateField, ['reserve', 'loss_num'])) {
            event(new StockUpdateEvent($sku));
        }
        // 更新目标去化率
        if ('goal_rate' === $updateField) {
            DailySalesReport::query()
                ->where('date', date('Y-m-d'))
                ->where('goods_sku_id', $sku->id)
                ->update([
                    'goal_rate' => $request->$updateField
                ]);
        }

        $this->addLog($id, $updateField);
        end:

        return response($this->res, $this->res['httpCode']);
    }

    public function store(Request $request)
    {
        if (!$request->hasFile('goodsSkus')) {
            $this->res = [
                'httpCode' => 404,
                'errorCode' => 404404,
                'errorMessage' => 'not found goodsSkus file',
            ];
        }
        try {
            $import = new GoodsSkusImport();
            $path = $request->file('goodsSkus');
            Excel::import($import, $path);
            $this->addLog(0, 'import');
        } catch (ValidationException $exception) {
            $this->setValidatorFailResponse($exception->validator->getMessageBag()->getMessages());
        }

        return response($this->res, $this->res['httpCode']);
    }

    public function export(Request $request)
    {
        $type = $request->get('exportType');
        ob_end_clean();
        if ('week_data' === $type) {
            $startDate = Carbon::now()->subWeek()->startOfWeek()->toDateString();
            $endDate = Carbon::now()->subWeek()->endOfWeek()->toDateString();
            return Excel::download(new WeekDataExport($startDate, $endDate), $startDate . '~' . $endDate . '.xlsx');
        }
        return Excel::download(new GoodsSkusExport($type), $type . '.xlsx');
    }

    public function newSetImport(Request $request)
    {
        if (!$request->hasFile('newSetFile')) {
            $this->res = [
                'httpCode' => 404,
                'errorCode' => 404404,
                'errorMessage' => 'not found new set file',
            ];
        }
        try {
            $import = new NewSetImport();
            $path = $request->file('newSetFile');
            Excel::import($import, $path);
            $this->addLog(0, 'import', 'newSetFile');
        } catch (ValidationException $exception) {
            $this->setValidatorFailResponse($exception->validator->getMessageBag()->getMessages());
        }

        return response($this->res, $this->res['httpCode']);
    }

    public function inventoryImport(Request $request)
    {
        if (!$request->hasFile('inventoryFile')) {
            $this->res = [
                'httpCode' => 404,
                'errorCode' => 404404,
                'errorMessage' => 'not found inventory file',
            ];
        }
        try {
            $import = new InventoryImport();
            $path = $request->file('inventoryFile');
            Excel::import($import, $path);
            $this->addLog(0, 'import', 'inventory');
        } catch (ValidationException $exception) {
            $this->setValidatorFailResponse($exception->validator->getMessageBag()->getMessages());
        }

        return response($this->res, $this->res['httpCode']);
    }

    public function stockNum()
    {
        $skus = GoodsSku::query()
            ->where('is_combination', 0)
            ->where('status', '>', 0)
            ->pluck('stock', 'external_sku_id')
            ->toArray();
        $roseNum = $otherNum = 0;
        foreach ($skus as $externalSkuId => $stock) {
            // 玫瑰
            if (false !== strpos($externalSkuId, 'M')) {
                $roseNum += $stock;
                continue;
            }
            // 多头玫瑰
            if (false !== strpos($externalSkuId, 'D')) {
                $roseNum += $stock;
                continue;
            }
            // 草花剔除A开头
            if (false !== strpos($externalSkuId, 'A')) {
                continue;
            }
            // 剔除Z开头组合
            if (false !== strpos($externalSkuId, 'Z')) {
                continue;
            }
            // 剔除测试CS
            if (false !== strpos($externalSkuId, 'CS')) {
                continue;
            }
            // 剔除N开头年宵花
//            if (false !== strpos($externalSkuId, 'N')) {
//                continue;
//            }
            $otherNum += $stock;
        }

        return response()->json([
            'rose_num' => $roseNum,
            'other_num' => $otherNum,
        ]);
    }

    public function goodsSkusList(Request $request)
    {
        $title = $request->input('title');
        $data = [];
        $goodsSkus = GoodsSku::query()
            ->with('goods:id,title')
            ->whereHas('goods', function ($query) use ($title) {
                $query->where('title', 'like', '%' . $title . '%');
            })
            ->get(['title', 'external_sku_id', 'goods_id']);
        foreach ($goodsSkus as $goodsSku) {
            $data[] = [
                'external_sku_id' => $goodsSku['external_sku_id'],
                'title' => $goodsSku['goods']['title'] . ' ' . $goodsSku['title']
            ];
        }

        return $data;
    }
}
