<?php


namespace App\Services;


use App\Logistic;
use App\Order;
use App\OrderCountingRecord;
use App\Warehouse;
use Carbon\Carbon;
use DateTime;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use App\User;
use Illuminate\Support\Str;


class OrderCountingRecordService
{
    /** @var $ownerService OwnerService */
    private $ownerService = null;

    function __construct()
    {
        $this->ownerService = app(OwnerService::class);
    }

    /**
     * @param $start
     * @param $end
     * @param null $ownerIds
     * @param string $unit
     * @param $user
     * @return mixed
     */
    public function get($start, $end, $ownerIds = null, $unit = '日', $user)
    {
        $resultByCache = $this->getByCache($start, $end, $ownerIds, $unit, $user);
        if (!($resultByCache['unExistingOrders'])) return $resultByCache['resultOrders'];
        $resultByOrderCountingRecords = $this->getByDatabase($resultByCache['unExistingOrders'], $unit);
        $this->createByDatabase($resultByOrderCountingRecords['unExistingOrders'], $unit);
        $this->getByDatabase($resultByCache['unExistingOrders'], $unit);
        $resultByCache = $this->getByCache($start, $end, $ownerIds, $unit, $user);
        return $resultByCache['resultOrders'];
    }

    public function orderCountingRecords($start, $end, $ownerIds = null, $unit = '日', $user = null)
    {
        $key = 'orderCountingRecords_' . $start . '_' . $end . '_' . $unit . '_' . Auth::user()->id;
        return Cache::remember($key, 600, function () use ($start, $end, $unit, $ownerIds, $user) {
            $orders = $this->get($start, $end, null, $unit, null);
            $dataList = collect();
            $orders->groupBy('date_target')->each(function ($items) use (&$dataList, $unit) {
                $counter = $items->reduce(function ($sum, $item) {
                    return $sum + $item->amount;
                }, 0);
                $date_target = $items[0]->date_target;
                if ($unit == '周') {
                    $date_target = (new DateTime())->setISODate(Str::of($date_target)->explode('-')[0], Str::of($date_target)->explode('-')[1])->format('yy-m-d');
                }
                $dataList->push([
                    'counter' => $counter,
                    'date_target' => $date_target,
                ]);
            });
            return $dataList->sortBy("date_target");
        });
    }

    /**
     * @param $start
     * @param $end
     * @param null $ownerIds
     * @param null $user
     * @return \Illuminate\Support\Collection|\Tightenco\Collect\Support\Collection
     */
    public function logisticsCountingRecords($start, $end, $ownerIds = null, $user = null)
    {
        $key = 'logisticsCountingRecords_' . $start . '_' . $end . '_' . Auth::user()->id;
        return Cache::remember($key, 600, function () use ($start, $end, $ownerIds, $user) {
            $dataList = collect();
            $resultOrders = $this->get($start, $end, $ownerIds, '日', $user);
            $resultOrders->groupBy('logistic_id')->each(function ($item) use (&$dataList) {
                $counter = $item->reduce(function ($sum, $item) {
                    return $sum + $item->amount;
                }, 0);
                $dataList->push([
                    'value' => $counter,
                    'logistic_id' => $item[0]->logistic_id,
                ]);
            });
            $map = [];
            $logistics = Logistic::query()->whereIn('id', data_get($dataList, '*.logistic_id'))->get();
            $logistics->each(function ($logistic) use (&$map) {
                $map[$logistic->id] = $logistic;
            });
            return $dataList->map(function (&$item) use ($map) {
                $logistic = $map[$item['logistic_id']] ?? '';
                $item['name'] = $logistic->name ?? '';
                return $item;
            });
        });
    }

    public function warehouseCountingRecords($start, $end, $ownerIds = null, $user = null)
    {
        $key = 'warehouseCountingRecords_' . $start . '_' . $end . '_' . Auth::user()->id;
        return Cache::remember($key, 600, function () use ($start, $end, $ownerIds, $user) {
            $dataList = collect();
            $resultOrders = $this->get($start, $end, $ownerIds, '日', $user);
            $resultOrders->groupBy('warehouse_id')->each(function ($item) use (&$dataList) {
                $counter = $item->reduce(function ($sum, $item) {
                    return $sum + $item->amount;
                }, 0);
                $dataList->push([
                    'value' => $counter,
                    'warehouse_id' => $item[0]->warehouse_id,
                ]);
            });
            $map = [];
            $logistics = Warehouse::query()->whereIn('id', data_get($dataList, '*.warehouse_id'))->get();
            $logistics->each(function ($warehouse) use (&$map) {
                $map[$warehouse->id] = $warehouse;
            });
            return $dataList->map(function (&$item) use ($map) {
                $warehouse = $map[$item['warehouse_id']] ?? '';
                $item['code'] = $warehouse->name ?? '';
                switch ($item['code']) {
                    case 'WH01':
                        $item['name'] = '松江一仓';
                        break;
                    case 'WH02':
                        $item['name'] = '松江二仓';
                        break;
                    case 'WH03':
                        $item['name'] = '嘉定一仓';
                        break;
                    default:
                        $item['name'] = '仓库为空';
                        break;
                }
                return $item;
            });
        });
    }

    /**
     * @param $start
     * @param $end
     * @param null $ownerIds
     * @param string $unit
     * @param $user
     * @return array
     */
    public function getByCache($start, $end, $ownerIds = null, $unit = '日', $user)
    {// Order[] results, Array [$date=>[$ownerIds]]
        $countingOwnerIds = $this->getCountingOwnerIds($ownerIds, $user);
        $resultOrders = collect();
        $unExistingOrders = [];
        $carbonInterfaces = $this->periodDateToArray($start, $end, $unit);
        foreach ($carbonInterfaces as $dateStr) {
            foreach ($countingOwnerIds as $ownerId) {
                $key = "order_counting_records_{$dateStr}_{$ownerId}_{$unit}";
                $orders = Cache::get($key);
                if ($orders) {
                    $orders->each(function ($item) use (&$resultOrders) {
                        $resultOrders->push($item);
                    });
                    continue;
                }
                $unExistingOrders[$dateStr][] = $ownerId;
            }
        }
        return ['resultOrders' => $resultOrders, 'unExistingOrders' => $unExistingOrders];
    }

    public function getByDatabase($targetOwnerIdsUnderDates, $unit = '日')
    {// Order[] results, Array [$date=>[$ownerIds]]
        $orderSqlBuilder = OrderCountingRecord::query();
        foreach ($targetOwnerIdsUnderDates as $date => $owners) {
            $orderSqlBuilder->orWhere(function ($query) use ($owners, $date, $unit) {
                $query->whereIn('owner_id', $owners)->where('date_target', $date)->where('counting_unit', $unit);
            });
        }
        $resultOrders = $orderSqlBuilder->get();
        $resultOrders->groupBy(['date_target', 'owner_id'])->each(function ($item, $dateStr) use ($unit) {
            $item->each(function ($item, $owner_id) use ($dateStr, $unit) {
                $key = "order_counting_records_{$dateStr}_{$owner_id}_{$unit}";
                $ttl = 3600 * 24;
                if ($dateStr==Carbon::now()->toDateString()) {
                    $ttl = 60;
                }
                Cache::put($key, $item, $ttl);
            });
        });
        $map = [];
        $resultOrders->each(function ($item) use (&$map) {
            $key = 'owner_id=' . $item->owner_id . ' date_target' . $item->date_target;
            $map[$key] = true;
        });
        foreach ($targetOwnerIdsUnderDates as $dateStr => $ownerIds) {
            foreach ($ownerIds as $key => $ownerId) {
                $key1 = 'owner_id=' . $ownerId . ' date_target' . $dateStr;
                if (isset($map[$key1])) {
                    unset($targetOwnerIdsUnderDates[$dateStr][$key]);
                }
            }
        }
        return ['resultOrders' => $resultOrders, 'unExistingOrders' => $targetOwnerIdsUnderDates];
    }

    public function createByDatabase($targetOwnerIdsUnderDates, $unit = '日')
    {// Order[] results]
        switch ($unit) {
            case '日':
                $resultOrders = $this->getCreateByDatabaseUnitDay($targetOwnerIdsUnderDates);
                break;
            case'周':
                $resultOrders = $this->getCreateByDatabaseUnitWeek($targetOwnerIdsUnderDates);
                break;
            case'月':
                $resultOrders = $this->getCreateByDatabaseUnitMonth($targetOwnerIdsUnderDates);
                break;
            default:
                $resultOrders = collect();
                break;
        }
        $result = collect();
        $resultOrders->each(function ($order) use (&$result, $unit) {
            $orderCountingRecord = new OrderCountingRecord([
                'owner_id' => $order->owner_id,
                'shop_id' => $order->shop_id,
                'warehouse_id' => $order->warehouse_id,
                'logistic_id' => $order->logistic_id,
                'date_target' => $order->date_target,
                'counting_unit' => $unit,
                'amount' => $order->amounts,
            ]);
            $result->push($orderCountingRecord);
        });
        OrderCountingRecord::query()->insert($result->toArray());
        return ['resultOrders' => $result];
    }


    public function periodDateToArray($start, $end, $unit = '日')
    {
        $dataArray = [];
        switch ($unit) {
            case '日';
                foreach (Carbon::parse($start)->daysUntil($end, 1)->toArray() as $item) {
                    $dataArray[] = $item->toDateString();
                }
                break;
            case '周';
                foreach (Carbon::parse($start)->weeksUntil($end, 1)->toArray() as $item) {
                    $dataArray[] = $item->year . '-' . $item->week . '';
                }
                break;
            case '月';
                foreach (Carbon::parse($start)->monthsUntil($end, 1)->toArray() as $item) {
                    $dataArray[] = $item->year . '-' . $item->format('m') . '';
                }
                break;
            case '年';
                foreach (Carbon::parse($start)->yearsUntil($end, 1)->toArray() as $item) {
                    $dataArray[] = $item->year . '-' . $item->month . '';
                }
                break;
            default:
                break;
        }
        return $dataArray;
    }

    /**
     * @param $ownerIds
     * @return array
     */
    public function getCountingOwnerIds($ownerIds = null, $user): array
    {
        if ($user == null) {
            $user = auth()->user();
        }
        /** @var UserService $userService */
        $userService = app('UserService');
        $permittingOwnerIds = $userService->getPermittingOwnerIds($user);
        if (!$ownerIds) {
            return $permittingOwnerIds;
        }
        return Cache::remember(
            'PermittingOwnerIds' . '_' . auth()->user()->id . '_' . implode('_', $ownerIds),
            600, function () use ($ownerIds, $permittingOwnerIds) {
            /** @var User $user */
            return array_intersect($ownerIds, $permittingOwnerIds);
        });
    }

    /**
     * @param $targetOwnerIdsUnderDates
     * @return \Illuminate\Database\Eloquent\Builder[]|Collection
     */
    private function getCreateByDatabaseUnitDay($targetOwnerIdsUnderDates)
    {
        $orderSqlBuilder = Order::query()->selectRaw("owner_id,warehouse_id,shop_id,logistic_id,count(1) as amounts ,DATE_FORMAT(created_at,'%Y-%m-%d') as date_target");
        foreach ($targetOwnerIdsUnderDates as $dateStr => $ownerIds) {
            $orderSqlBuilder->orWhere(function ($query) use ($ownerIds, $dateStr) {
                $query->whereIn('owner_id', $ownerIds)->whereDate('created_at', $dateStr)->where('wms_status', '订单完成');
            });
        }
        return $orderSqlBuilder->groupBy(['owner_id', 'warehouse_id', 'shop_id', 'logistic_id', 'date_target'])->get();
    }

    private function getCreateByDatabaseUnitWeek($targetOwnerIdsUnderDates)
    {
        $orderSqlBuilder = Order::query()->selectRaw("owner_id,warehouse_id,shop_id,logistic_id,count(1) as amounts ,DATE_FORMAT(created_at,'%x-%v') as date_target");
        foreach ($targetOwnerIdsUnderDates as $dateStr => $ownerIds) {
            $orderSqlBuilder->orWhere(function ($query) use ($ownerIds, $dateStr) {
                $dateStr = (new DateTime())->setISODate(Str::of($dateStr)->explode('-')[0], Str::of($dateStr)->explode('-')[1])->format('yy-m-d');
                $query->whereIn('owner_id', $ownerIds)->whereDate('created_at', '>=', $dateStr)->whereDate('created_at', '<', Carbon::parse($dateStr)->addWeek()->toDateString())->where('wms_status', '订单完成');;
            });
        }
        return $orderSqlBuilder->groupBy(['owner_id', 'warehouse_id', 'shop_id', 'logistic_id', 'date_target'])->get();
    }

    private function getCreateByDatabaseUnitMonth($targetOwnerIdsUnderDates)
    {
        $orderSqlBuilder = Order::query()->selectRaw("owner_id,warehouse_id,shop_id,logistic_id,count(1) as amounts ,DATE_FORMAT(created_at,'%Y-%m') as date_target");
        foreach ($targetOwnerIdsUnderDates as $dateStr => $ownerIds) {
            $orderSqlBuilder->orWhere(function ($query) use ($ownerIds, $dateStr) {
                $year = Str::of($dateStr)->explode('-')[0];
                $month = Str::of($dateStr)->explode('-')[1];
                $query->whereIn('owner_id', $ownerIds)->whereMonth('created_at', $month)->whereYear('created_at', $year)->where('wms_status', '订单完成');;
            });
        }
        return $orderSqlBuilder->groupBy(['owner_id', 'warehouse_id', 'shop_id', 'logistic_id', 'date_target'])->get();
    }
}
