<?php

namespace App\Http\Controllers\API;

use App\Enums\DeductionType;
use App\Enums\DiscountType;
use App\Http\Controllers\Controller;
use App\Http\Requests\CartRequest;
use App\Http\Requests\CheckoutRequest;
use App\Http\Requests\VoucherRequest;
use App\Http\Resources\ColorResource;
use App\Http\Resources\CouponResource;
use App\Http\Resources\SizeResource;
use App\Models\AdminCoupon;
use App\Models\City;
use App\Models\Product;
use App\Models\Shop;
use App\Repositories\CouponRepository;
use App\Repositories\ProductRepository;
use App\Repositories\ShopRepository;
use App\Repositories\VatTaxRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Number;

class GuestCartController extends Controller
{
    private Collection $carts;

    private Collection $products;

    private Collection $shops;

    private Collection $collectedCoupons;

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request)
    {
        $this->setCartsFromRequest($request);

        return $this->createResponse('cart list', [], 200);
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(CartRequest $request)
    {
        $this->setCartsFromRequest($request);
        $product = ProductRepository::find($request->product_id);

        if (! $product) {
            return $this->createResponse('Sorry! product not found', [], 422);
        }

        $quantity = $request->quantity ?? 1;
        $cart = $this->carts->where('product_id', $product->id)->first();

        if (($product->quantity < $quantity) || ($cart && $product->quantity <= $cart['quantity'])) {
            return $this->createResponse('Sorry! product cart quantity is limited. No more stock', [], 422);
        }

        $this->storeOrUpdateByRequest($request, $product);

        return $this->createResponse('product added to cart', [], 200);
    }

    /**
     * increase cart quantity
     */
    public function increment(CartRequest $request)
    {
        $this->setCartsFromRequest($request);
        $product = $this->products->where('id', $request->product_id)->first();
        $cart = $this->carts->where('product_id', $product->id)->first();

        if (! $cart) {
            return $this->createResponse('Sorry product not found in cart', [], 422);
        }

        $quantity = $cart['quantity'];
        $flashSale = $product->flashSales?->first();
        $flashSaleProduct = $flashSale?->products()->where('id', $product->id)->first();

        $productQty = $product->quantity;

        if ($flashSaleProduct) {
            $flashSaleQty = $flashSaleProduct->pivot->quantity - $flashSaleProduct->pivot->sale_quantity;

            if ($flashSaleQty > 0) {
                $productQty = $flashSaleQty;
            }
        }

        if ($productQty > $quantity) {
            $this->carts->transform(function ($c) use ($cart) {
                if ($c['product_id'] == $cart['product_id']) {
                    $c['quantity'] += 1;
                }

                return $c;
            });
        } else {
            return $this->createResponse('Sorry! product cart quantity is limited. No more stock', [], 422);
        }

        return $this->createResponse('product quantity increased', [], 200);
    }

    /**
     * decrease cart quantity
     * */
    public function decrement(CartRequest $request)
    {
        $this->setCartsFromRequest($request);
        $cart = $this->carts->where('product_id', $request->product_id)->first();

        if (! $cart) {
            return $this->createResponse('Sorry product not found in cart', [], 422);
        }

        $message = 'product removed from cart';

        if ($cart['quantity'] > 1) {
            $this->carts->transform(function ($c) use ($cart) {
                if ($c['product_id'] == $cart['product_id']) {
                    $c['quantity'] -= 1;
                }

                return $c;
            });

            $message = 'product quantity decreased';
        } else {
            $this->carts = $this->carts->reject(function ($c) use ($cart) {
                return $c['product_id'] == $cart['product_id'];
            });
        }

        return $this->createResponse($message, [], 200);
    }

    public function destroy(CartRequest $request)
    {
        $this->setCartsFromRequest($request);
        $cart = $this->carts->where('product_id', $request->product_id)->first();

        if (! $cart) {
            return $this->sendResponse('Sorry product not found in cart', [], 422);
        }

        $this->carts = $this->carts->reject(function ($c) use ($cart) {
            return $c['product_id'] == $cart['product_id'];
        });

        return $this->createResponse('product removed from cart', [], 200);
    }

    public function getVouchers(VoucherRequest $request)
    {
        $shopId = $request->shop_id;

        $coupons = CouponRepository::query()->whereShopId($shopId)->Active()->whereNull('limit_for_user')->isValid()->get();

        return $this->json('Shop vouchers', [
            'coupons' => CouponResource::collection($coupons),
        ]);
    }

    public function checkout(CheckoutRequest $request)
    {
        $this->setCartsFromRequest($request);
        $groupCart = $this->carts->groupBy('shop_id');
        $this->shops = ShopRepository::query()->whereIn('id', $groupCart->keys())->get();

        $this->collectedCoupons = CouponRepository::query()
            ->Active()
            ->isValid()
            ->whereNull('limit_for_user')
            ->whereIn('id', $request->collected_coupons)
            ->get();

        $shopIds = $request->shop_ids ?? [];
        $carts = $this->carts->whereIn('shop_id', $shopIds);
        $checkout = $this->checkoutByRequest($request, $carts);

        $groupCart = $carts->groupBy('shop_id');
        $shopWiseProducts = $this->shopWiseCartProducts($groupCart);

        $message = 'Checkout information';

        $applyCoupon = false;

        if ($request->coupon_code && $checkout['coupon_discount'] > 0) {
            $applyCoupon = true;
            $message = 'Coupon applied';
        } elseif ($request->coupon_code) {
            $message = 'Coupon not applied';
        }

        return $this->createResponse($message, [
            'checkout' => $checkout,
            'apply_coupon' => $applyCoupon,
            'checkout_items' => $shopWiseProducts,
            'collected_coupons' => $this->collectedCoupons->pluck('id')->toArray(),
        ], 200);
    }

    private function shopWiseCartProducts($groupCart)
    {
        if (! isset($this->shops)) {
            $this->shops = ShopRepository::query()->whereIn('id', $groupCart->keys())->get();
        }

        $shopWiseProducts = collect([]);

        foreach ($groupCart as $key => $products) {
            $productArray = collect([]);

            foreach ($products as $cart) {
                $product = $this->products->where('id', $cart['product_id'])->first();
                $discountPercentage = $product->getDiscountPercentage($product->price, $product->discount_price);
                $totalSold = $product->orders->sum('pivot.quantity');

                $flashsale = $product->flashSales?->first();
                $flashsaleProduct = null;
                $quantity = null;

                if ($flashsale) {
                    $flashsaleProduct = $flashsale?->products()->where('id', $product->id)->first();

                    $quantity = $flashsaleProduct?->pivot->quantity - $flashsaleProduct->pivot->sale_quantity;

                    if ($quantity == 0) {
                        $quantity = null;
                        $flashsaleProduct = null;
                    } else {
                        $discountPercentage = $flashsale?->pivot->discount;
                    }
                }

                $size = $product->sizes()?->where('id', $cart['size'])->first();
                $color = $product->colors()?->where('id', $cart['color'])->first();

                $sizePrice = $size?->pivot?->price ?? 0;
                $colorPrice = $color?->pivot?->price ?? 0;
                $extraPrice = $sizePrice + $colorPrice;

                $discountPrice = $product->discount_price > 0 ? ($product->discount_price + $extraPrice) : 0;
                if ($flashsaleProduct) {
                    $discountPrice = $flashsaleProduct->pivot->price + $extraPrice;
                }

                $mainPrice = $product->price + $extraPrice;

                // calculate vat taxes
                $priceTaxAmount = 0;
                $discountTaxAmount = 0;
                foreach ($product->vatTaxes ?? [] as $tax) {
                    if ($tax->percentage > 0) {
                        $priceTaxAmount += $mainPrice * ($tax->percentage / 100);
                        $discountPrice > 0 ? $discountTaxAmount += $discountPrice * ($tax->percentage / 100) : null;
                    }
                }

                $mainPrice += $priceTaxAmount;
                $discountPrice > 0 ? $discountPrice += $discountTaxAmount : null;

                if ($discountPrice > 0) {
                    $discountPercentage = ($mainPrice - $discountPrice) / $mainPrice * 100;
                }

                $lang = request()->header('accept-language') ?? 'en';

                $translation = $product->translations()?->where('lang', $lang)->first();
                $name = $translation?->name ?? $product->name;

                $brandTranslation = $product->brand?->translations()?->where('lang', $lang)->first();
                $brandName = $brandTranslation?->name ?? $product->brand?->name;

                $productArray[] = (object) [
                    'id' => $product->id,
                    'quantity' => (int) $cart['quantity'],
                    'name' => $name,
                    'thumbnail' => $product->thumbnail,
                    'brand' => $brandName,
                    'price' => (float) number_format($mainPrice, 2, '.', ''),
                    'discount_price' => (float) number_format($discountPrice, 2, '.', ''),
                    'discount_percentage' => (float) number_format($discountPercentage, 2, '.', ''),
                    'rating' => (float) $product->averageRating,
                    'total_reviews' => (string) Number::abbreviate($product->reviews->count(), maxPrecision: 2),
                    'total_sold' => (string) number_format($totalSold, 0, '.', ','),
                    'color' => $color ? ColorResource::make($color) : null,
                    'size' => $size ? SizeResource::make($size) : null,
                    'unit' => $cart['unit'],
                ];
            }

            $shop = $this->shops->where('id', $key)->first();

            $shopWiseProducts[] = (object) [
                'shop_id' => $key,
                'shop_name' => $shop->name,
                'shop_logo' => $shop->logo,
                'shop_rating' => (float) $shop->averageRating,
                'products' => $productArray,
            ];
        }

        return $shopWiseProducts;
    }

    private function storeOrUpdateByRequest(CartRequest $request, Product $product): array
    {
        $size = $request->size ?? null;
        $color = $request->color ?? null;
        $unit = $request->unit ?? $product->unit?->name;

        $cart = $this->carts->where('product_id', $product->id)->first();

        if ($cart) {
            $cart['quantity'] = $cart['quantity'] + 1;
            $cart['size'] = $request->size ?? $cart['size'];
            $cart['color'] = $request->color ?? $cart['color'];
            $cart['unit'] = $request->unit ?? $cart['unit'];

            $this->carts->transform(function ($c) use ($cart) {
                if ($c['product_id'] == $cart['product_id']) {
                    return $cart;
                }

                return $c;
            });

            return $cart;
        }

        $this->products->add($product);

        $cart = [
            'product_id' => $request->product_id,
            'shop_id' => $product->shop->id,
            'quantity' => $request->quantity ?? 1,
            'size' => $size,
            'color' => $color,
            'unit' => $unit,
        ];

        $this->carts->add($cart);

        return $cart;
    }

    private function checkoutByRequest($request, $carts)
    {
        $totalAmount = 0;
        $deliveryCharge = 0;
        $couponDiscount = 0;
        $payableAmount = 0;
        $taxAmount = 0;

        $shopWiseTotalAmount = [];
        $totalOrderTaxAmount = 0;

        $city = $request->city_id ? City::find($request->city_id) : null;
        $generalSetting = generaleSetting();
        if (! $carts->isEmpty()) {
            foreach ($carts as $cart) {
                $product = $this->products->where('id', $cart['product_id'])->first();
                $flashSale = $product->flashSales?->first();
                $flashSaleProduct = null;
                $quantity = null;

                $price = $product->discount_price > 0 ? $product->discount_price : $product->price;

                if ($flashSale) {
                    $flashSaleProduct = $flashSale?->products()->where('id', $product->id)->first();

                    $quantity = $flashSaleProduct?->pivot->quantity - $flashSaleProduct->pivot->sale_quantity;

                    if ($quantity == 0) {
                        $quantity = null;
                        $flashSaleProduct = null;
                    } else {
                        $price = $flashSaleProduct->pivot->price;
                    }
                }

                $sizePrice = $product->sizes()?->where('id', $cart['size'])->first()?->pivot?->price ?? 0;
                $price = $price + $sizePrice;

                $colorPrice = $product->colors()?->where('id', $cart['color'])->first()?->pivot?->price ?? 0;
                $price = $price + $colorPrice;

                foreach ($product->vatTaxes ?? [] as $tax) {
                    if ($tax->percentage > 0) {
                        $taxAmount += $price * ($tax->percentage / 100);
                    }
                }
                $price += $taxAmount;

                // get shop wise total amount
                $shop = $product->shop;
                if (array_key_exists($shop->id, $shopWiseTotalAmount)) {
                    $currentAmount = $shopWiseTotalAmount[$shop->id];
                    $shopWiseTotalAmount[$shop->id] = $currentAmount + ($price * $cart['quantity']);
                } else {
                    $shopWiseTotalAmount[$shop->id] = $price * $cart['quantity'];
                }

                $totalAmount += $price * $cart['quantity'];
            }

            $groupCarts = $carts->groupBy('shop_id');

            // get delivery charge
            $deliveryCharge = 0;

            foreach ($groupCarts as $shopCarts) {
                $productQty = 0;

                foreach ($shopCarts as $cart) {
                    $productQty += $cart['quantity'];
                }

                if ($productQty > 0) {
                    $deliveryCharge += getDeliveryCharge($productQty);
                }
            }

            // generate array for get discount
            $products = collect([]);
            foreach ($carts as $cart) {
                $products->push([
                    'id' => $cart['product_id'],
                    'quantity' => (int) $cart['quantity'],
                    'shop_id' => $cart['shop_id'],
                ]);
            }
            $array = (object) [
                'coupon_code' => $request->coupon_code,
                'products' => $products,
            ];

            // get coupon discount
            $getDiscount = $this->getCouponDiscount($array);
            $couponDiscount = $getDiscount['discount_amount'];

            $deliveryCharge = $generalSetting?->default_delivery_charge ?? 0;
            if ($city) {
                $deliveryCharge = $city->delivery_charge;
                if ($city->amount > 0 && $totalAmount >= $city->amount) {
                    $deliveryCharge = 0;
                }
            }

            // dd($deliveryCharge);

            // get order base tax
            $orderBaseTax = VatTaxRepository::getOrderBaseTax();
            foreach ($shopWiseTotalAmount as $shopId => $subtotal) {
                if ($orderBaseTax && $orderBaseTax->deduction == DeductionType::EXCLUSIVE->value && $orderBaseTax->percentage > 0) {
                    $vatTaxAmount = $subtotal * ($orderBaseTax->percentage / 100);
                    $totalOrderTaxAmount += $vatTaxAmount;
                }
            }

            $payableAmount = ($totalAmount + $deliveryCharge + $totalOrderTaxAmount) - $couponDiscount;
        }

        return [
            'total_amount' => (float) round($totalAmount, 2),
            'delivery_charge' => (float) round($deliveryCharge, 2),
            'coupon_discount' => (float) round($couponDiscount, 2),
            'payable_amount' => (float) round($payableAmount, 2),
            'order_tax_amount' => (float) round($totalOrderTaxAmount, 2),
        ];
    }

    private function getCouponDiscount($request)
    {
        $productsCollection = collect($request->products);
        $shopProducts = $productsCollection->groupBy('shop_id');

        $totalOrderAmount = 0; // total amount
        $totalDiscountAmount = 0;

        $coupon = null;

        $couponCode = $request->coupon_code ?? null;

        foreach ($shopProducts as $shopId => $products) {
            $totalAmount = 0; // total amount

            foreach ($products as $productArray) {
                $product = Product::find($productArray['id']);
                $totalAmount += (float) ($product->discount_price > 0 ? $product->discount_price : $product->price) * $productArray['quantity'];
            }

            if ($couponCode) {
                $shop = Shop::find($shopId);
                $coupon = $shop->coupons()->whereNull('limit_for_user')->where('code', $couponCode)->Active()->isValid()->first();

                if (! $coupon) {
                    $coupon = AdminCoupon::where('shop_id', $shopId)->whereHas('coupon', function ($query) use ($couponCode) {
                        $query->whereNull('limit_for_user')->where('code', $couponCode)->Active()->isValid();
                    })->first()?->coupon;
                }

                if ($coupon) {
                    $discount = self::getCouponDiscountAmount($coupon, $totalAmount);

                    $totalOrderAmount += (float) $discount['total_amount'];
                    $totalDiscountAmount += (float) $discount['discount_amount'];
                }
            } else {
                $collectedCoupons = $this->collectedCoupons->where('shop_id', $shopId);

                $adminCoupons = AdminCoupon::where('shop_id', $shopId)->whereHas('coupon', function ($query) {
                    $query->whereNull('limit_for_user')->Active()->isValid();
                })->get();

                foreach ($adminCoupons as $adminCoupon) {
                    if (in_array($adminCoupon->coupon_id, $this->collectedCoupons->pluck('id')->toArray())) {
                        $collectedCoupons->push($adminCoupon->coupon);
                    }
                }

                foreach ($collectedCoupons as $collectedCoupon) {
                    $discount = $this->getCouponDiscountAmount($collectedCoupon, $totalAmount);

                    $totalOrderAmount += (float) $discount['total_amount'];

                    if ($discount['discount_amount'] > 0) {
                        $coupon = $collectedCoupon;
                        $totalDiscountAmount += (float) $discount['discount_amount'];
                        break;
                    }
                }
            }
        }

        return [
            'total_amount' => $totalOrderAmount,
            'discount_amount' => $totalDiscountAmount,
            'coupon' => $coupon,
        ];
    }

    private function getCouponDiscountAmount($coupon, $totalAmount)
    {
        $amount = $coupon->type->value == DiscountType::PERCENTAGE->value ? ($totalAmount * $coupon->discount) / 100 : $coupon->discount;
        $couponDiscount = 0;

        if ($coupon->min_amount <= $totalAmount) {
            $couponDiscount = $amount;

            if ($coupon->max_discount_amount && $coupon->max_discount_amount < $amount) {
                $couponDiscount = $coupon->max_discount_amount;
            }
        }

        return [
            'total_amount' => $totalAmount,
            'discount_amount' => (float) round($couponDiscount ?? 0, 2),
        ];
    }

    private function setCartsFromRequest($request)
    {
        $carts = collect($request->carts);
        $this->products = ProductRepository::query()->whereIn('id', $carts->pluck('product_id'))->get();

        // Remove products that have been deleted by shop owner but are still in the user's cart
        $this->carts = $carts->reject(function ($c) {
            return $this->products->where('id', $c['product_id'])->isEmpty();
        });
    }

    private function createResponse($message, $data, $status = 200)
    {
        // We always return carts in the frontend to ensure the cart products are up to date with the database
        // This is necessary because some products might have been deleted
        $groupCart = $this->carts->groupBy('shop_id');
        $shopWiseProducts = $this->shopWiseCartProducts($groupCart);

        $data['total'] = $this->carts->count();
        $data['cart_items'] = $shopWiseProducts;
        $data['carts'] = $this->carts->values();

        return $this->json($message, $data, $status);
    }
}
