<?php
declare(strict_types=1);

namespace App\Services\OrderService;

use App\Events\Order\SendOrder;
use DB;
use Log;
use Exception;
use Throwable;
use App\Models\Shop;
use App\Models\User;
use App\Models\Order;
use App\Models\Point;
use App\Models\Payment;
use App\Helpers\Utility;
use App\Models\Language;
use App\Jobs\PayReferral;
use App\Models\Transaction;
use App\Models\Translation;
use App\Models\PointHistory;
use App\Traits\Notification;
use App\Traits\PaymentRefund;
use App\Models\WalletHistory;
use App\Services\CoreService;
use App\Helpers\ResponseError;
use App\Models\PushNotification;
use Stripe\Exception\ApiErrorException;
use App\Services\EmailSettingService\EmailSendService;
use App\Services\WalletHistoryService\WalletHistoryService;

class OrderStatusUpdateService extends CoreService
{
    use Notification, PaymentRefund;

    /**
     * @return string
     */
    protected function getModelClass(): string
    {
        return Order::class;
    }

    /**
     * @param Order $order
     * @param array $data
     * @param bool $isDelivery
     * @return array
     */
    public function statusUpdate(Order $order, array $data, bool $isDelivery = false): array
    {
        $status = $data['status'];

        if ($order->status == $status) {
            return [
                'status'  => false,
                'code'    => ResponseError::ERROR_252,
                'message' => __('errors.' . ResponseError::ERROR_252, locale: $this->language)
            ];
        }

        try {
            $this->updateNotes($order, $data);

            $order = DB::transaction(function () use ($order, $status) {

                if ($status == Order::STATUS_DELIVERED) {

                    $this->adminWalletTopUp($order);

                    $point = Point::getActualPoint($order->total_price);

                    Utility::topUpCashBack($point, $order, $this->language);

                    if ($order?->transaction?->paymentSystem?->tag === 'cash') {
                        $order->transaction?->update([
                            'status' => Transaction::STATUS_PAID
                        ]);
                    }

                    PayReferral::dispatchAfterResponse($order->user, 'increment');
                }

                $totalPrice = $order->total_price;

                if ($status == Order::STATUS_CANCELED && $order->orderRefunds?->count() === 0) {
                    $this->refund($order);
                }

                $order->update([
                    'status'         => $status,
                    'canceled_note'  => request()->input('canceled_note'),
                    'total_price'    => $totalPrice,
                    'current'        => in_array($status, [Order::STATUS_DELIVERED, Order::STATUS_CANCELED]) ? 0 : $order->current,
                ]);

                return $order->fresh(['shop']);
            });

            if (in_array($order->status, $order->shop?->email_statuses ?? [])) {
                (new EmailSendService)->sendOrder($order);
            }

            event(new SendOrder($order));

        } catch (Throwable $e) {

            $this->error($e);

            return [
                'status'  => false,
                'code'    => ResponseError::ERROR_501,
                'message' => $e->getMessage()
            ];
        }

        $default = Language::where('default', 1)->first()?->locale;

        /** @var Order $order */
        $this->statusUpdateNotify($order, $isDelivery);

        $seller = $order->shop?->seller;

        $tStatus = Translation::where(function ($q) use ($seller, $default) {
            $q->where('locale', $seller->lang ?? $this->language)->orWhere('locale', $default);
        })
            ->where('key', $status)
            ->first()
            ?->value;

        $replace = [
            'id'     => $order->id,
            'status' => $tStatus ?? $status,
        ];

        if (in_array($status, [Order::STATUS_ON_A_WAY, Order::STATUS_DELIVERED, Order::STATUS_CANCELED]) && $seller) {

            $this->sendNotification(
                $order,
                $seller->firebase_token ?? [],
                __('errors.' . ResponseError::STATUS_CHANGED, $replace, $seller->lang ?? $this->language),
                __('errors.' . ResponseError::STATUS_CHANGED, $replace, $seller->lang ?? $this->language),
                [
                    'id'     => $order->id,
                    'status' => $order->status,
                    'type'   => PushNotification::STATUS_CHANGED
                ],
                [$seller->id]
            );

        } else if (
            in_array($status, [Order::STATUS_ACCEPTED, Order::STATUS_READY])
            && $seller
            && $order->shop->delivery_type === Shop::DELIVERY_TYPE_IN_HOUSE
        ) {

            $this->sendNotification(
                $order,
                $seller->firebase_token ?? [],
                __('errors.' . ResponseError::STATUS_CHANGED, $replace, $seller->lang ?? $this->language),
                __('errors.' . ResponseError::STATUS_CHANGED, $replace, $seller->lang ?? $this->language),
                [
                    'id'     => $order->id,
                    'status' => $order->status,
                    'type'   => PushNotification::STATUS_CHANGED
                ],
                [$seller->id]
            );

        }

        return ['status' => true, 'code' => ResponseError::NO_ERROR, 'data' => $order];
    }

    /**
     * @param Order $order
     * @return void
     * @throws Throwable
     */
    private function adminWalletTopUp(Order $order): void
    {
        /** @var User $admin */
        $admin = User::with('wallet')->whereHas('roles', fn($q) => $q->where('name', 'admin'))->first();

        if (!$admin->wallet) {
            Log::error("admin #$admin?->id doesnt have wallet");
            return;
        }

        $request = request()->merge([
            'type'   => 'topup',
            'price'  => $order->total_price,
            'note'   => "For Seller Order #$order->id",
            'status' => WalletHistory::PAID,
            'user'   => $admin,
        ])->all();

        (new WalletHistoryService)->create($request);
    }

    /**
     * @param Order $order
     * @param array $data
     * @return void
     * @throws Exception
     */
    public function updateNotes(Order $order, array $data): void
    {
        try {
            $notes = $order->notes?->where('status', $data['status'])?->first()?->notes ?? [];

            if (isset($data['notes'])) {
                foreach ($data['notes'] as $key => $value) {
                    $value['created_at'] = $value['created_at'] ?? now()->format('Y-m-d H:i:s') . 'Z';
                    $notes[$key] = $value;
                }
            }

            $order->notes()->updateOrCreate([
                'status' => $data['status'],
            ], [
                'notes'      => $notes,
                'created_at' => now(),
                'updated_at' => now(),
            ]);

        } catch (Throwable $e) {
            throw new Exception($e->getMessage() . $e->getLine() . $e->getFile());
        }
    }

    /**
     * @throws Throwable
     * @throws ApiErrorException
     */
    private function refund($order): void
    {
        if ($order->transaction->paymentSystem->tag != Payment::TAG_CASH) {
            $this->paymentRefund($order);
        }

        if ($order->transaction->paymentSystem->tag != Payment::TAG_CASH) {
           return;
        }

        if ($order->pointHistories?->count() > 0) {
            foreach ($order->pointHistories as $pointHistory) {
                /** @var PointHistory $pointHistory */
                $order->user?->wallet?->decrement('price', $pointHistory->price);
                $pointHistory->delete();
            }
        }

        if ($order->status === Order::STATUS_DELIVERED) {
            PayReferral::dispatchAfterResponse($order->user, 'decrement');
        }
    }
}
