<?php

namespace App\Http\Middleware;

use App\Helpers\UUIDHelper;
use App\Models\Consignment;
use App\Models\ConsignmentItem;
use App\Models\Order;
use App\Models\OrderGroup;
use App\Models\Setting;
use App\Models\Transaction;
use App\Notifications\abandonedCart;
use Carbon\Carbon;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;

class HandleCartMiddleware
{
    use UUIDHelper;

    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $lock = Cache::lock('handle_cart_lock', 600);
        if ($lock->get()) {
            try {
                // delete expired consignment items
                $this->deleteExpiredConsignmentItems();

                // delete empty consignments
                $this->deleteEmptyConsignments();

                // delete empty orders
                $this->deleteEmptyOrders();

                // delete empty order groups
                $this->deleteEmptyOrderGroups();

                // delete expired waiting_payment transaction
                $this->deleteExpiredTransactions();

                // change status for delivered orders
                $this->changeStatusForDeliveredOrders();

                // abandoned cart notification
                $this->abandonedCartNotification();
            } finally {
                $lock->release();
            }
        }

        return $next($request);
    }

    /**
     * delete expired consignment items
     */
    public function deleteExpiredConsignmentItems()
    {
        $consignmentItems = ConsignmentItem::whereHas('consignment', function ($query) {
            $query->whereHas('order', function ($query) {
                $query->where('status', 'awaiting');
            });
        })->where(function ($query) {
            $query->where('status', 'ready_to_send')->OrWhere('status', 'awaiting_request_from_seller');
        })->where('updated_at', '<=', Carbon::now()->subDays(15)->toDateTimeString())->get();

        foreach ($consignmentItems as $consignmentItem) {
            $consignmentItem->delete();
        }
    }

    /**
     * delete empty consignments
     */
    public function deleteEmptyConsignments()
    {
        $consignments = Consignment::doesntHave('consignmentItems')->delete();
    }

    /**
     * delete empty orders
     */
    public function deleteEmptyOrders()
    {
        $orders = Order::where('status', 'awaiting')->doesntHave('consignments')->delete();
    }

    /**
     * delete empty orders
     */
    public function deleteEmptyOrderGroups()
    {
        $orderGroups = OrderGroup::doesntHave('orders')->delete();
    }

    /**
     * delete empty orders
     */
    public function deleteExpiredTransactions()
    {
        $transactions = Transaction::where('status', 'waiting_payment')->where('updated_at', '<=', Carbon::now()->subMinutes(12)->toDateTimeString())->delete();
    }

    /**
     * change sent orders to delivered
     */
    public function changeStatusForDeliveredOrders()
    {
        $releaseOfOrderAmounts = Cache::remember('setting_release_of_order_amounts', now()->addMonth(), function () {
            return Setting::where('key', 'release_of_order_amounts')->first();
        });
        $releaseOfOrderAmounts = $releaseOfOrderAmounts != null ? (int)str_replace(',', '', $releaseOfOrderAmounts->value) : null;

        $sentOrders = Order::where('status', 'sent')->where('sent_at', '<=', Carbon::now()->subDays($releaseOfOrderAmounts)->toDateTimeString())->get();
        foreach ($sentOrders as $order) {
            $order->update([
                'status' => 'delivered'
            ]);

            foreach ($order->consignments()->where('status', '!=', 'cancelled')->where('status', '!=', 'returned')->get() as $consignment) {
                $consignment->update([
                    'status' => 'delivered'
                ]);

                foreach ($consignment->consignmentItems()->where('status', '!=', 'cancelled')->where('status', '!=', 'returned')->get() as $consignmentItem) {
                    $consignmentItem->update([
                        'status' => 'delivered'
                    ]);
                }
            }
        }
    }

    /**
     * abandoned Cart Notification
     */
    public function abandonedCartNotification()
    {
        $awaitingOrders = Order::where('status', 'awaiting')->get();

        if ($awaitingOrders != null && count($awaitingOrders) > 0) {
            // get abandoned cart from settings
            $abandonedCart = Cache::remember('setting_abandoned_cart', now()->addMonth(), function () {
                return Setting::where('key', 'abandoned_cart')->first();
            });
            $abandonedCart = $abandonedCart != null ? unserialize($abandonedCart->value) : ['status' => false, 'time_period' => []];

            if ($abandonedCart != null) {
                // check abandoned cart status
                if ($abandonedCart['status'] && count($abandonedCart['time_period']) > 0) {

                    foreach ($awaitingOrders as $order) {
                        $orderLock = Cache::lock('abandoned_cart_order_' . $order->id, 20);
                        if (!$orderLock->get()) {
                            continue;
                        }
                        try {
                            $orderMinutesPassed = $order->created_at->diffInMinutes(Carbon::now());
                            $orderAbandonedCart = $order->abandoned_cart ? unserialize($order->abandoned_cart) : [];

                            // find largest time period at this time
                            $maxTimePeriod = null;
                            foreach ($abandonedCart['time_period'] as $tp) {
                                $timePeriod = (int)$tp;
                                if ($orderMinutesPassed >= $timePeriod && !in_array($timePeriod, $orderAbandonedCart)) {
                                    // comparission for find large time period
                                    if ($maxTimePeriod === null || $timePeriod > $maxTimePeriod) {
                                        $maxTimePeriod = $timePeriod;
                                    }
                                }
                            }

                            // check maxTimePeriod exists or not
                            if ($maxTimePeriod !== null) {
                                $newAbandonedCart = $orderAbandonedCart;
                                // sort time_period array
                                $sortedTimePeriods = array_map('intval', $abandonedCart['time_period']);
                                sort($sortedTimePeriods);

                                // add all small time period to passed
                                foreach ($sortedTimePeriods as $timePeriod) {
                                    if ($timePeriod <= $maxTimePeriod && !in_array($timePeriod, $newAbandonedCart)) {
                                        $newAbandonedCart[] = $timePeriod;
                                    }
                                }

                                // update order
                                $order->update([
                                    'abandoned_cart' => serialize($newAbandonedCart),
                                ]);

                                // send notification
                                $user = $order->user;
                                $user->notify(new abandonedCart($user->get_name, $order->uuid));
                            }
                        } finally {
                            $orderLock->release();
                        }
                    }
                }
            }
        }
    }
}
