<?php

namespace Daylight\Core\Models;

use Daylight\Core\Models\Enums\StockStatus;
use Daylight\Core\Modules\MediaLibrary\Contracts\HasMedia;
use Daylight\Core\Modules\MediaLibrary\DefaultMedia;
use Daylight\Core\Modules\MediaLibrary\Models\Concerns\InteractsWithMedia;
use Daylight\Core\Modules\Pricing\Facades\Pricing;
use Daylight\Core\Modules\Pricing\PricingManager;
use Daylight\Core\Services\Tax;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Context;

class Variant extends Model implements HasMedia
{
    use InteractsWithMedia;
    use SoftDeletes;

    protected $guarded = [];

    /**
     * Touch the parent product to trigger Scout re-indexing.
     */
    protected $touches = ['product'];

    protected $casts = [
        'purchasable' => 'boolean',
        'backorders' => 'boolean',
    ];

    protected $with = [];

    public function product(): BelongsTo
    {
        return $this->belongsTo(daylightModel('product'));
    }

    public function optionValues(): BelongsToMany
    {
        return $this->belongsToMany(daylightModel('option_value'))->withTimestamps();
    }

    public function shippingClasses(): BelongsToMany
    {
        return $this->belongsToMany(daylightModel('shipping_class'));
    }

    public function taxClass(): BelongsTo
    {
        return $this->belongsTo(daylightModel('tax_class'));
    }

    public function variationName(): Attribute
    {
        $this->loadMissing([
            'optionValues.translations',
            'optionValues.option.translations',
        ]);

        return Attribute::make(
            get: function () {
                return $this->optionValues->map(function (OptionValue $optionValue) {
                    return sprintf('%s: %s',
                        $optionValue->option->name,
                        $optionValue->name
                    );
                })->reverse()->join(' / ');
            }
        );
    }

    public function isPurchasable(): bool
    {
        return $this->purchasable;
    }

    public function inStock(): bool
    {
        return $this->stock > 0;
    }

    public function hasBackorders(): bool
    {
        return $this->backorders;
    }

    public function stockStatus(): Attribute
    {
        return Attribute::make(
            get: function () {
                if ($this->inStock()) {
                    return StockStatus::IN_STOCK;
                }

                if ($this->hasBackorders()) {
                    return StockStatus::BACKORDER;
                }

                return StockStatus::OUT_OF_STOCK;
            }
        );
    }

    public function variantName(): Attribute
    {
        return Attribute::make(
            get: function () {
                return $this->optionValues->map(function (OptionValue $optionValue) {
                    return sprintf('%s: %s',
                        $optionValue->option->name,
                        $optionValue->name
                    );
                })->join(' / ');
            }
        );
    }

    public function priceIncludingVat(): Attribute
    {
        return Attribute::make(
            get: function () {
                return $this->pricing()->get()->matched?->price * (1 + Tax::defaultTaxRateForClass($this->tax_class_id) / 100);
            }
        );
    }

    public function getMedia(?string $field = null): Collection
    {
        $this->loadMissing('media.conversions');

        if ($this->media->isEmpty()) {
            return $this->product->media ?? collect();
        }

        return $this->media->where('pivot.properties.field', $field);
    }

    public function getFirstMedia(?string $field = null)
    {
        return $this->getMedia($field)->first() ?? DefaultMedia::make();
    }

    public function prices(): HasMany
    {
        return $this->hasMany(daylightModel('price'));
    }

    /**
     * The base price (no customer group, no customer, single unit) that is active
     * on the given date. Base prices are date-ranged, so the most recent active
     * range wins; falls back to the most recent range when none are active.
     */
    public function currentBasePrice(?Carbon $at = null): ?Model
    {
        $at = $at ?? now();

        $bases = $this->prices
            ->whereNull('customer_group_id')
            ->whereNull('customer_id')
            ->where('min_quantity', 1);

        $active = $bases->filter(function ($price) use ($at) {
            $startsOk = is_null($price->starts_at) || $at->greaterThanOrEqualTo(Carbon::parse($price->starts_at));
            $endsOk = is_null($price->ends_at) || $at->toDateString() <= Carbon::parse($price->ends_at)->toDateString();

            return $startsOk && $endsOk;
        });

        return ($active->isNotEmpty() ? $active : $bases)
            ->sortByDesc('starts_at')
            ->first();
    }

    /**
     * Prices scoped to the current customer/group/date via Context.
     * Falls back to unfiltered prices() when Context is not populated (artisan, tests, admin).
     */
    public function scopedPrices(): HasMany
    {
        if (Context::missing('pricing.is_populated')) {
            return $this->prices();
        }

        $customerGroupIds = Context::get('pricing.customer_group_ids', []);
        $customerId = Context::get('pricing.customer_id');
        $now = Context::get('pricing.now');

        return $this->prices()
            ->where(function ($query) use ($customerGroupIds, $customerId) {
                $query->where(function ($q) {
                    $q->whereNull('customer_group_id')->whereNull('customer_id');
                })
                    ->orWhereIn('customer_group_id', $customerGroupIds);

                if ($customerId) {
                    $query->orWhere('customer_id', $customerId);
                }
            })
            ->where(function ($query) use ($now) {
                $query->whereNull('starts_at')->orWhere('starts_at', '<=', $now);
            })
            ->where(function ($query) use ($now) {
                $query->whereNull('ends_at')->orWhereDate('ends_at', '>=', $now);
            });
    }

    public function basePrices(): HasMany
    {
        return $this->prices()->whereMinQuantity(1)->whereNull('customer_group_id');
    }

    public function priceBreaks(): HasMany
    {
        return $this->prices()->where('min_quantity', '>', 1);
    }

    public function pricing(): PricingManager
    {
        return Pricing::for($this);
    }

    public function getDataLayerInformation(int $quantity = 1): array
    {
        return [
            'item_id' => $this->product->id,
            'item_name' => $this->product->name,
            'item_brand' => $this->product->brand->name,
            'item_category' => $this->product->categories->first()?->name,
            'item_variant' => $this->sku,
            'quantity' => $quantity,
            'price' => $this->pricing()->get()->matched->price / 100,
            'currency' => 'EUR',
        ];
    }
}
