<?php

namespace Daylight\Core\Modules\Pricing;

use Daylight\Core\Models\CustomerGroup;
use Daylight\Core\Models\Variant;
use Daylight\Core\Modules\Pricing\Contracts\PricingManagerInterface;
use Daylight\Core\Modules\Pricing\DTO\Price;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;

class PricingManager implements PricingManagerInterface
{
    public Price $price;

    public ?Authenticatable $user = null;

    public Variant $purchasable;

    public int $quantity = 1;

    public ?Collection $customerGroups = null;

    private static array $cache = [];

    public function __construct()
    {
        if (auth()->check()) {
            $this->user = auth()->user()->loadMissing('customer.customerGroups');
            $cacheKey = 'user_'.$this->user->id.'_customer_groups';
            if (! isset(self::$cache[$cacheKey])) {
                self::$cache[$cacheKey] = $this->user->customer->customerGroups;
            }
            $this->customerGroups = self::$cache[$cacheKey];
        }
    }

    public function for(Variant $variant): self
    {
        $this->purchasable = $variant;

        return $this;
    }

    public function user(?Authenticatable $user): self
    {
        $this->user = $user;

        return $this;
    }

    public function guest(): self
    {
        $this->user = null;

        return $this;
    }

    public function quantity(int $quantity): self
    {
        $this->quantity = $quantity;

        return $this;
    }

    public function customerGroups(?Collection $customerGroups): self
    {
        $this->customerGroups = $customerGroups;

        return $this;
    }

    public function customerGroup(?CustomerGroup $customerGroup): self
    {
        $this->customerGroups(
            collect([$customerGroup])
        );

        return $this;
    }

    public function get(): Price
    {
        if (! $this->purchasable) {
            throw new \ErrorException('No purchasable set.');
        }

        if (! $this->customerGroups || ! $this->customerGroups->count()) {
            $cacheKey = 'default_customer_groups';
            if (! isset(self::$cache[$cacheKey])) {
                self::$cache[$cacheKey] = collect([CustomerGroup::getDefault()]);
            }
            $this->customerGroups = self::$cache[$cacheKey];
        }

        if ($this->user && $this->user->customer) {
            if ($this->user->customer->customerGroups->count()) {
                $cacheKey = 'user_'.$this->user->id.'_customer_groups';
                if (! isset(self::$cache[$cacheKey])) {
                    self::$cache[$cacheKey] = $this->user->customer->customerGroups;
                }
                $this->customerGroups = self::$cache[$cacheKey];
            }
        }

        $prices = $this->purchasable->prices->filter(function ($price) {
            return ! $price->customer_group_id || $this->customerGroups->pluck('id')->contains($price->customer_group_id);
        })->sortBy('price');

        $basePrice = $prices->first(fn ($price) => $price->min_quantity == 1 && ! $price->customer_group_id);
        $matched = $basePrice;

        $potentialGroupPrice = $prices->filter(function ($price) {
            return (bool) $price->customer_group_id && ($price->min_quantity == 1);
        })->sortBy('price');

        $matched = $potentialGroupPrice->first() ?: $matched;

        $priceBreaks = $prices->filter(function ($price) {
            return $price->min_quantity > 1 && $this->quantity >= $price->min_quantity;
        })->sortBy('price');

        $matched = $priceBreaks->first() ?: $matched;

        if (! $matched) {
            throw new \ErrorException('No price set.');
        }

        $this->price = new Price(
            matched: $matched,
            base: $prices->first(fn ($price) => $price->min_quantity == 1),
            priceBreaks: $prices->filter(fn ($price) => $price->min_quantity > 1),
            customerGroupPrices: $prices->filter(fn ($price) => (bool) $price->customer_group_id)
        );

        $this->reset();

        return $this->price;
    }

    private function reset()
    {
        $this->quantity = 1;
        $this->customerGroups = null;
    }
}
