<?php

namespace App\Services;

use App\Jobs\AutoStopTrip;
use App\Models\{Area, Scooter, Trip};
use App\Notifications\Api\App\{GiftNotification, ReferralGiftNotification};
use Exception;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\DB;
use MatanYadaev\EloquentSpatial\Enums\Srid;
use MatanYadaev\EloquentSpatial\Objects\Point;

class TripService
{
    public function scooterInsideArea($scooter_id)
    {
        $scooter = $this->getActiveScooterWithTrip($scooter_id);

        if (!$scooter) {
            return [null, null];
        }

        [$lat, $lng] = $this->getLocation($scooter);
        $point = new Point((float) $lat, (float) $lng, Srid::WGS84->value);

        $scooter_inside_area = $scooter->whereHas('area', function ($query) use ($point) {
            $query->whereContains('area', $point);
        })->first();

        return [$scooter, $scooter_inside_area];
    }

    public function canEnd($scooter_id)
    {
        $scooter = $this->getActiveScooterWithTrip($scooter_id, true);

        if (!$scooter) {
            return null;
        }

        [$lat, $lng] = $this->getLocation($scooter);
        $point = new Point((float) $lat, (float) $lng, Srid::WGS84->value);

        return $scooter->whereHas('area', function ($query) use ($point) {
            $query->whereContains('area', $point);
        })->first();
    }

    public function canTakGift($scooter_id, $lat, $lng)
    {
        $point = new Point((float) $lat, (float) $lng, Srid::WGS84->value);

        return Scooter::where('is_active', true)
            ->whereHas('trips', function ($query) {
                $query->where(['status' => 'started', 'user_id' => auth('api')->id()]);
            })
            ->whereHas('area', function ($query) use ($point) {
                $query->whereContains('area', $point)
                    ->whereHas('stopAreas', function ($query) use ($point) {
                        $query->whereContains('area', $point);
                    });
            })
            ->where(function ($query) use ($scooter_id) {
                $query->where('id', $scooter_id)->orWhere('number', $scooter_id);
            })
            ->first();
    }

    public static function start($trip, $status)
    {
        DB::beginTransaction();
        try {
            $scooter = $trip->scooter;
            [$lat, $lng, $location] = (new self)->getLocation($scooter);
            $first_location = ['first_lat' => $lat, 'first_lng' => $lng, 'first_location' => $location];

            $trip->update(['status' => $status, 'status_times' => [$status => now()]] + $first_location);

            $times_by_minutes = PriceService::calculateTimeBasedOnWalletBalance($trip->scooter, $trip->user);

            AutoStopTrip::dispatch($trip->id)->delay(now()->addMinutes(max($times_by_minutes - 5, 0)));

            DB::commit();

            return $trip->fresh();
        } catch (Exception $e) {
            DB::rollBack();
            info($e);
            throw new HttpResponseException(response()->json(['status' => 'fail', 'data' => null, 'message' => trans('Something went wrong, please try again')], 422));
        }
    }

    public static function end($trip, $status)
    {
        DB::beginTransaction();
        try {
            $scooter                        = $trip->scooter;
            [$lat, $lng, $location]         = (new self)->getLocation($scooter);
            [$price_before_discount, $time] = PriceService::calculateTripPriceAndTime($trip);

            $price                          = $price_before_discount;
            $discount                       = 0;

            if ($trip->coupon_id) {
                [$price, $discount] = CouponService::applyCoupon($trip->coupon_id, $price_before_discount);
            }

            $transaction = (new WalletService($trip->user))->withdrawal($price, Trip::class, $trip->id);

            if ($scooter = (new TripService())->canTakGift($trip->scooter->scooter_code, $lat, $lng)) {
                (new WalletService())->deposit($scooter->gift_mony, Trip::class, $trip->id);
            }

            $end_location = ['end_lat' => $lat, 'end_lng' => $lng, 'end_location' => $location];

            $end_area_id  = Area::whereContains('area', new Point($lat, $lng, Srid::WGS84->value))->first();

            $trip->update([
                'end_area_id'           => $end_area_id?->id,
                'gift_mony'             => $scooter?->gift_mony ?? 0,
                'time'                  => $time,
                'price_before_discount' => $price_before_discount,
                'discount_amount'       => $discount,
                'price'                 => $price,
                'status'                => $status,
                'status_times'          => [$status => now()]
            ] + $end_location);

            $trip->scooter->update(['in_order' => false]);

            DB::commit();

            if ($scooter) {
                $trip->user->notify(new GiftNotification($transaction->id, $scooter->gift_mony, ['database', 'fcm']));
            }

            if ((new self())->isFirstCompletedTripWithReferrer($trip->id) && setting('referral_gift')) {
                $transaction = (new WalletService($trip->user->referrer))->addReferralBonus(setting('referral_gift'), Trip::class, $trip->id);
                $trip->user->referrer->notify(new ReferralGiftNotification($transaction->id, $scooter->gift_mony, ['database', 'fcm']));
            }

            return $trip->fresh();
        } catch (Exception $e) {
            DB::rollBack();
            info($e);
            throw new HttpResponseException(response()->json(['status' => 'fail', 'data' => null, 'message' => trans('Something went wrong, please try again')], 422));
        }
    }

    public function getLocation($scooter)
    {
        $scooter_location = $scooter->location ?? $scooter->init_location;
        $lat = $scooter_location->latitude;
        $lng = $scooter_location->longitude;
        $location = (new GoogleMapService())->getGeoCoding($lat, $lng);

        return [$lat, $lng, $location];
    }

    public function isFirstCompletedTripWithReferrer($trip_id)
    {
        $trip = Trip::findOrFail($trip_id);

        return $trip->user->referrer
            && $trip->user->trips()
            ->where('status', 'ended')
            ->count() === 1;
    }

    private function getActiveScooterWithTrip($scooter_id, $checkUser = false)
    {
        return Scooter::where('is_active', true)
            ->whereHas('trips', function ($query) use ($checkUser) {
                $query->where('status', 'started');
                if ($checkUser && auth('api')->check()) {
                    $query->where('user_id', auth('api')->id());
                }
            })
            ->where(function ($query) use ($scooter_id) {
                $query->where('id', $scooter_id)->orWhere('number', $scooter_id)->orWhere('serial_number', $scooter_id);
            })
            ->first();
    }
}
