<?php

namespace App\Services;

use App\Constants\AreaType;
use App\Models\Area;
use App\Models\DictLabour;
use App\Models\Order;
use App\Models\OrderPart;
use App\Models\OrderPartArea;
use App\Models\WorkerTime;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class OrderService
{
    /**
     * @var DictService
     */
    private DictService $dictService;

    public function __construct(DictService $dictService)
    {
        $this->dictService = $dictService;
    }

    /**
     * Load base order info by ids
     *
     * @param array|null $ids
     * @return Collection
     */
    public function loadOrders(array|null $ids): Collection
    {
        if($ids) {
            return Order::whereIn('id', $ids)->orderBy('number')->get();
        }
        else {
            return Order::orderBy('number')->get();
        }
    }

    /**
     * Load grouped orders by dates
     *
     * @param Carbon $from
     * @param Carbon $to
     * @return mixed
     */
    public function load(Carbon $from, Carbon $to): mixed
    {
        $orderPartAreas = OrderPartArea::select('order_part_areas.*')
            ->leftJoin('areas', function($join) {
                $join->on('areas.id', '=', 'order_part_areas.area_id');
            })
            ->where('areas.active', true)
            ->where(function($query) use ($from, $to) {
                $query->where('order_part_areas.plan_date', '>=', $from)
                    ->where('order_part_areas.plan_date', '<=', $to);
            })
            //->orWhere('order_part_areas.plan_date', null)
            ->get();

        $orderPartIds = [];
        $orderPartAreasMap = [];
        foreach($orderPartAreas as $area) {
            if (!in_array($area->order_part_id, $orderPartIds)) {
                $orderPartIds[] = $area->order_part_id;
                $orderPartAreasMap[$area->order_part_id] = [];
            }
            $orderPartAreasMap[$area->order_part_id][] = $area;
        }

        $orderIds = [];
        $orderParts = OrderPart::whereIn('id', $orderPartIds)->get();
        $orderPartsMap = [];
        foreach($orderParts as $part) {
            if (!in_array($part->order_id, $orderIds)) {
                $orderIds[] = $part->order_id;
                $orderPartsMap[$part->order_id] = [];
            }
            $part->areas = $orderPartAreasMap[$part->id];
            $orderPartsMap[$part->order_id][] = $part;
        }

        $orders = Order::select('id', 'number', 'labour_id', 'addition_work', 'addition_percent')
            ->whereIn('id', $orderIds)
            ->whereIn('order_type_id', [1, 2, 3]) //filter by order types
            ->orderBy('number')
            ->get();
        foreach($orders as $order) {
            $order->parts = collect($orderPartsMap[$order->id]);
        }

        return $orders;
    }

    /**
     * Load order counts by areas
     *
     * @param array $orderIds
     * @param array $areaIds
     * @return mixed
     */
    public function loadCounts(array $orderIds, array $areaIds): mixed
    {
        $orders = Order::select('id')->whereIn('id', $orderIds)->get();

        $countList = OrderPartArea::select('order_part_areas.area_id', 'order_parts.order_id', DB::raw('SUM(order_part_areas.count) as count'))
            ->leftJoin('order_parts', function($join) {
                $join->on('order_parts.id', '=', 'order_part_areas.order_part_id');
            })
            ->whereIn('order_part_areas.area_id', $areaIds)
            ->whereIn('order_parts.order_id', $orderIds)
            ->groupBy('order_part_areas.area_id', 'order_parts.order_id')
            ->get();

        $orderCountMap = [];
        foreach($countList as $item) {
            if($item->count) {
                if(!isset($orderCountMap[$item->order_id])) $orderCountMap[$item->order_id] = [];
                $orderCountMap[$item->order_id][] = [
                    'area_id' => $item->area_id,
                    'count' => $item->count,
                ];
            }
        }

        foreach($orders as $order) {
            $order->areas = $orderCountMap[$order->id] ?? [];
        }

        return $orders;
    }

    /**
     * Load order area counts grouped by statuses
     *
     * @param Carbon $from
     * @param Carbon $to
     * @param array $areaIds
     * @return mixed
     */
    public function loadSummary(Carbon $from, Carbon $to, array $areaIds): mixed
    {
        $orderMap = [];

        $planDateList = OrderPartArea::select('order_part_areas.area_id', 'order_parts.order_id', DB::raw('SUM(order_part_areas.count)'))
            ->leftJoin('order_parts', function($join) {
                $join->on('order_parts.id', '=', 'order_part_areas.order_part_id');
            })
            ->whereIn('order_part_areas.area_id', $areaIds)
            ->where('order_part_areas.plan_date', '>=', $from)
            ->where('order_part_areas.plan_date', '<=', $to)
            ->groupBy('order_part_areas.area_id', 'order_parts.order_id')
            ->get();

        foreach($planDateList as $item) {
            if(!isset($orderMap[$item->order_id])) $orderMap[$item->order_id] = [];
            if(!isset($orderMap[$item->order_id][$item->area_id])) $orderMap[$item->order_id][$item->area_id] = [];
            if(!isset($orderMap[$item->order_id][$item->area_id]['plan'])) $orderMap[$item->order_id][$item->area_id]['plan'] = 0;
            $orderMap[$item->order_id][$item->area_id]['plan'] += $item->sum;
        }

        $planNoDateList = OrderPartArea::select('order_part_areas.area_id', 'order_parts.order_id', DB::raw('SUM(order_part_areas.count)'))
            ->leftJoin('order_parts', function($join) {
                $join->on('order_parts.id', '=', 'order_part_areas.order_part_id');
            })
            ->whereIn('order_part_areas.area_id', $areaIds)
            ->where('order_part_areas.plan_date', null)
            ->groupBy('order_part_areas.area_id', 'order_parts.order_id')
            ->get();

        foreach($planNoDateList as $item) {
            if(!isset($orderMap[$item->order_id])) $orderMap[$item->order_id] = [];
            if(!isset($orderMap[$item->order_id][$item->area_id])) $orderMap[$item->order_id][$item->area_id] = [];
            if(!isset($orderMap[$item->order_id][$item->area_id]['no_plan'])) $orderMap[$item->order_id][$item->area_id]['no_plan'] = 0;
            $orderMap[$item->order_id][$item->area_id]['no_plan'] += $item->sum;
        }

        $finishDateList = OrderPartArea::select('order_part_areas.area_id', 'order_parts.order_id', DB::raw('SUM(order_part_areas.count)'))
            ->leftJoin('order_parts', function($join) {
                $join->on('order_parts.id', '=', 'order_part_areas.order_part_id');
            })
            ->whereIn('order_part_areas.area_id', $areaIds)
            ->where('order_part_areas.finish_date', '>=', $from)
            ->where('order_part_areas.finish_date', '<=', $to)
            ->groupBy('order_part_areas.area_id', 'order_parts.order_id')
            ->get();

        foreach($finishDateList as $item) {
            if(!isset($orderMap[$item->order_id])) $orderMap[$item->order_id] = [];
            if(!isset($orderMap[$item->order_id][$item->area_id])) $orderMap[$item->order_id][$item->area_id] = [];
            if(!isset($orderMap[$item->order_id][$item->area_id]['finish'])) $orderMap[$item->order_id][$item->area_id]['finish'] = 0;
            $orderMap[$item->order_id][$item->area_id]['finish'] += $item->sum;
        }

        $orders = Order::select('id', 'labour_id', 'addition_work')->whereIn('id', array_keys($orderMap))->get()->toArray();
        $orderLabours = [];
        $orderAdditionWork = [];
        foreach ($orders as $order) {
            $orderLabours[$order['id']] = $order['labour_id'];
            $orderAdditionWork[$order['id']] = $order['addition_work'];
        }

        $additionArea = Area::where('type', AreaType::ADDITION_WORK)->first();
        $additionWorkerTimes = WorkerTime::select('order_id', DB::raw('SUM(time) as time'))
            ->where('area_id', $additionArea->id)
            ->whereIn('order_id', array_keys($orderMap))    
            ->groupBy('order_id')
            ->get();
        foreach($additionWorkerTimes as $additionWorkerTime) {
            if(isset($orderAdditionWork[$additionWorkerTime->order_id])) {
                $orderAdditionWork[$additionWorkerTime->order_id] -= $additionWorkerTime->time;
            }
        }

        $labours = [];
        foreach ($this->dictService->loadLabourHours(array_keys($orderMap)) as $labour) {
            $labours[$labour['id']] = $labour;
        }

        $result = [];
        foreach ($orderMap as $orderId => $areaMap) {
            $labourId = $orderLabours[$orderId] ?? 0;
            $labour = $labours[$labourId] ?? null;
            if($labour) {
                foreach($areaMap as $areaId => $data) {
                    foreach ($labour['areas'] as $labourArea) {
                        if($labourArea['id'] == $areaId) {
                            $plan = ($data['plan'] ?? 0) * $labourArea['time'];
                            $noPlan = ($data['no_plan'] ?? 0) * $labourArea['time'];
                            $finish = ($data['finish'] ?? 0) * $labourArea['time'];
                            $result[] = [
                                'area_id' => $areaId,
                                'order_id' => $orderId,
                                'plan' => $plan,
                                'no_plan' => $noPlan,
                                'finish' => $finish,
                            ];
                            break;
                        }
                    }
                }
                if(isset($orderAdditionWork[$orderId])) {
                    $result[] = [
                        'area_id' => $additionArea->id,
                        'order_id' => $orderId,
                        'plan' => 0,
                        'no_plan' => $orderAdditionWork[$orderId],
                        'finish' => $orderAdditionWork[$orderId],
                    ];
                }
            }
        }

        return $result;
    }

    /**
     * Update order by ID
     *
     * @param  int  $id
     * @param  array  $data
     * @return Order
     */
    public function update(int $id, array $data): Order
    {
        $order = Order::findOrFail($id);
        if($order) {
            $order->update($data);
        }
        return $order->refresh();
    }

    /**
     * Update order links
     *
     * @param array $orders
     * @param DictLabour $labour
     * @return void
     */
    public function updateOrderLinks(array $orders, DictLabour $labour): void
    {
        $orderIds = [];
        foreach($orders as $order) {
            $orderIds[] = $order['order_id'];
            $this->updateAddition(Order::find($order['order_id']), $order['addition_work']);
        }

        $oldOrders = Order::where('labour_id', $labour->id)->get();
        $processed = [];
        foreach ($oldOrders as $order) {
            if(in_array($order->id, $orderIds)) {
                $processed[] = $order->id;
            }
            else {
                $order->update([
                    'labour_id' => null,
                ]);
            }
        }
        foreach ($orderIds as $orderId) {
            if(!in_array($orderId, $processed)) {
                $order = Order::find($orderId);
                if($order) {
                    $order->update([
                        'labour_id' => $labour->id,
                    ]);
                }
            }
        }
    }

    /**
     * Delete order links
     *
     * @param DictLabour $labour
     * @return void
     */
    public function deleteOrderLinks(DictLabour $labour): void
    {
        $orders = Order::where('labour_id', $labour->id)->get();
        foreach ($orders as $order) {
            $order->update([
                'labour_id' => null,
            ]);
        }
    }

    /**
     * Update order addition percent
     *
     * @param Order $order
     * @param float|null $work
     * @return void
     */
    public function updateAddition(Order $order, float|null $work): void
    {
        if($work) {
            $count = OrderPartArea::select(DB::raw('SUM(order_part_areas.count) as count'))
                ->leftJoin('order_parts', function($join) {
                    $join->on('order_parts.id', '=', 'order_part_areas.order_part_id');
                })
                ->where('order_parts.order_id', $order->id)
                ->groupBy('order_parts.order_id')
                ->first();

            $percent = ($count && ($count->count > 0)) ? ($work / $count->count) : null;
            $order->update([
                'addition_work' => $work,
                'addition_percent' => $percent,
            ]);
        }
        else {
            $order->update([
                'addition_work' => null,
                'addition_percent' => null,
            ]);
        }
    }
}
