<?php

namespace Daylight\Core\Modules\Cart;

use App\Models\User;
use Daylight\Core\Models\Address;
use Daylight\Core\Models\Country;
use Daylight\Core\Models\Variant;
use Daylight\Core\Modules\Cart\Contracts\CartInterface;
use Daylight\Core\Modules\Cart\Models\Cart as CartModel;
use Daylight\Core\Services\Countries;
use Illuminate\Session\SessionManager;
use Illuminate\Support\Collection;

class Cart implements CartInterface
{
    protected ?CartModel $instance = null;

    protected bool $hasShippingCosts = true;

    protected string $shippingMethod = 'shipping';

    protected ?Country $billingCountry = null;

    protected ?Country $shippingCountry = null;

    protected ?Address $billingAddress = null;

    protected ?Address $shippingAddress = null;

    protected string $sessionKey = 'cart_session';

    public function __construct(
        protected SessionManager $sessionManager
    ) {
        //
    }

    public function exists(): bool
    {
        return $this->sessionManager->has($this->sessionKey) && $this->instance();
    }

    public function destroy(): void
    {
        $this->instance()->delete();

        $this->sessionManager->forget($this->sessionKey);
    }

    public function removeAll(): void
    {
        $this->instance()->variants()->detach();
    }

    public function associate(User $user): void
    {
        if (! $user) {
            return;
        }

        $this->instance()->user()->associate($user);
        $this->instance()->save();
    }

    public function create(?User $user = null): CartModel
    {
        $instance = CartModel::make();

        if ($user) {
            $instance->user()->associate($user);
        }

        $instance->save();

        $this->sessionManager->put($this->sessionKey, $instance->uuid);

        return $instance;
    }

    public function contents(): Collection
    {
        return $this->instance()->variants;
    }

    public function count(): int
    {
        return $this->contents()->count();
    }

    public function isEmpty(): bool
    {
        return $this->count() === 0;
    }

    public function add(Variant $variant, int $quantity = 1): void
    {
        if ($existingVariant = $this->getVariant($variant)) {
            $quantity += $existingVariant->pivot->quantity;

            $this->changeQuantity($variant, $quantity);

            return;
        }

        $this->instance()->variants()->syncWithoutDetaching([
            $variant->id => [
                'quantity' => $quantity,
                //                'quantity' => min($quantity, $variant->stock),
            ],
        ]);
    }

    public function remove(Variant|int $variant): void
    {
        $variantId = $variant instanceof Variant ? $variant->id : $variant;

        $this->instance()->variants()->detach($variantId);
    }

    public function changeQuantity(Variant|int $variant, int $quantity): void
    {
        $variantId = $variant instanceof Variant ? $variant->id : $variant;

        $this->instance()->variants()->updateExistingPivot($variantId, [
            'quantity' => $quantity,
            //            'quantity' => min($quantity, $variant->stock),
        ]);
    }

    public function getQuantityForVariant(Variant $variant): int
    {
        return $this->instance()->variants->find($variant->id)?->pivot->quantity ?? 0;
    }

    public function getVariant(Variant $variant): ?Variant
    {
        return $this->instance()->variants->find($variant->id);
    }

    public function subtotal(): int
    {
        return $this->instance()->variants
            ->reduce(function ($carry, Variant $variant) {
                return intval($carry) + ($variant->pricing()->quantity($variant->pivot->quantity)->get()->matched->getRawOriginal('price') * $variant->pivot->quantity);
            }, 0);
    }

    public function total(): int
    {
        return $this->subtotal() + $this->taxes()->sum('amount') + ($this->hasShipping() ? $this->shipping() : 0);
    }

    public function weight(): int
    {
        return $this->instance()->variants
            ->reduce(function ($carry, Variant $variant) {
                return intval($carry) + ($variant->weight * $variant->pivot->quantity);
            }, 0);
    }

    public function taxes(): Collection
    {
        if ($this->billingAddress()?->hasExemptedVat()) {
            return collect([
                [
                    'rate' => 0,
                    'amount' => 0,
                    'name' => __('daylight::general.vat'),
                ],
            ]);
        }

        $variants = $this->instance()->variants
            ->groupBy('tax_class_id')
            ->map(function ($variants, $class) {
                $taxRate = $this->billingCountry()->taxRates->where('tax_class_id', $class)->first();

                $subtotal = $variants->reduce(function ($carry, Variant $variant) {
                    return intval($carry) + ($variant->pricing()->quantity($variant->pivot->quantity)->get()->matched->getRawOriginal('price') * $variant->pivot->quantity);
                }, 0);

                $tax = $subtotal * ($taxRate->rate / 100);

                return [
                    'rate' => $taxRate->rate,
                    'amount' => $tax,
                    'name' => $taxRate->name,
                ];
            });

        if (! $this->hasShipping()) {
            return $variants;
        }

        return collect([
            ...$variants,
            //            ...$this->getShippingCostsCalculator()->calculateTotalShippingTax(),
        ])
            ->groupBy('rate')
            ->map(function ($taxes) {
                return [
                    'rate' => $taxes->first()['rate'],
                    'amount' => $taxes->sum('amount'),
                    'name' => $taxes->first()['name'],
                ];
            });
    }

    public function shippingInformation(): Collection
    {
        //        return $this->getShippingCostsCalculator()->shippingRules();
        return collect();
    }

    public function getShippingMethod(): string
    {
        return $this->shippingMethod;
    }

    public function setShippingMethod(string $method): void
    {
        $this->shippingMethod = $method;

        if ($method === 'pickup') {
            $this->hasShippingCosts = false;
        }
    }

    public function hasShipping(): bool
    {
        return $this->hasShippingCosts;
    }

    public function shipping(): int
    {
        return 0;
        //        return $this->getShippingCostsCalculator()->calculateTotalShippingCost();
    }

    public function setBillingCountry(Country $country)
    {
        $this->billingCountry = $country;

        return $this;
    }

    public function setShippingCountry(Country $country)
    {
        $this->shippingCountry = $country;

        return $this;
    }

    public function billingCountry(): Country
    {
        if ($this->billingCountry) {
            return $this->billingCountry;
        }

        return Countries::defaultCountry();
    }

    public function shippingCountry(): Country
    {
        if ($this->shippingCountry) {
            return $this->shippingCountry;
        }

        return Countries::defaultCountry();
    }

    public function billingAddress()
    {
        if ($this->billingAddress) {
            return $this->billingAddress;
        }

        return $this->instance()->user?->customer?->defaultBillingAddress;
    }

    public function shippingAddress()
    {
        if ($this->shippingAddress) {
            return $this->shippingAddress;
        }

        return $this->instance()->user?->customer?->defaultBillingAddress;
    }

    public function setBillingAddress(Address $address): void
    {
        $this->billingAddress = $address;
    }

    public function setShippingAddress(Address $address): void
    {
        $this->shippingAddress = $address;
    }

    public function getCrossSellingProducts(int $amount = 4): Collection
    {
        return $this->instance()->variants
            ->flatMap(function (Variant $variant) {
                return $variant->product->crossSells;
            })
            ->unique('id')
            ->take($amount);
    }

    public function instance(): CartModel
    {
        if ($this->instance) {
            return $this->instance;
        }

        $existingInstance = CartModel::query()
            ->whereUuid($this->sessionManager->get($this->sessionKey))
            ->with([
                'variants.media',
                'variants.shippingClasses.taxClass',
                'variants.optionValues.translations' => function ($query) {
                    $query->where('locale', $this->sessionManager->pull('locale') ?? app()->getLocale());
                },
                'variants.optionValues.option.translations' => function ($query) {
                    $query->where('locale', $this->sessionManager->pull('locale') ?? app()->getLocale());
                },
                'variants.product.translations' => function ($query) {
                    $query->where('locale', $this->sessionManager->pull('locale') ?? app()->getLocale());
                },
                'variants.product.crossSells.target.translations' => function ($query) {
                    $query->where('locale', $this->sessionManager->pull('locale') ?? app()->getLocale());
                },
                'user.customer.customerGroups',
                'user.customer.defaultBillingAddress.country.taxRates',
                'user.customer.defaultBillingAddress.country.shippingZones',
            ])
            ->first();

        if (! $existingInstance) {
            return $this->instance = $this->create();
        }

        return $this->instance = $existingInstance;
    }
}
