<?php

namespace Daylight\Core\Models;

use Daylight\Core\Models\Concerns\SyncsSearchIndex;
use Daylight\Core\Models\Contracts\Relatable;
use Daylight\Core\Models\Enums\AssociationType;
use Daylight\Core\Models\Enums\ProductStatus;
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\MediaLibrary\Models\Media;
use Daylight\Core\Modules\MediaLibrary\Models\Pivot\Attachable;
use Daylight\Core\Modules\Multilingualism\Contracts\HasTranslations;
use Daylight\Core\Modules\Multilingualism\Models\Concerns\InteractsWithTranslations;
use Daylight\Core\Modules\Multilingualism\Multilingualism;
use Illuminate\Database\Eloquent\Builder;
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\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use Laravel\Scout\Searchable;

class Product extends Model implements HasMedia, HasTranslations, Relatable
{
    use InteractsWithMedia;
    use InteractsWithTranslations;
    use Searchable;
    use SoftDeletes;
    use SyncsSearchIndex;

    protected $guarded = [];

    protected $casts = [
        'status' => ProductStatus::class,
    ];

    public function translatedFields(): array
    {
        return ['name', 'description', 'slug'];
    }

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

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

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

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

    public function variants(): HasMany
    {
        return $this->hasMany(daylightModel('variant'))->orderBy('order');
    }

    public function options(): HasMany
    {
        return $this->hasMany(daylightModel('option'))->orderBy('order');
    }

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

    public function crossSells(): HasMany
    {
        return $this->associations()->where('type', AssociationType::CROSS_SELL->value);
    }

    public function upSells(): HasMany
    {
        return $this->associations()->where('type', AssociationType::UP_SELL->value);
    }

    public function inverseAssociations(): HasMany
    {
        return $this->hasMany(daylightModel('product_association'), 'target_id');
    }

    public function defaultVariation(): HasOne
    {
        return $this->variants()->one();
    }

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

    public function defaultTranslation(): HasOne
    {
        return $this
            ->hasOne(daylightModel('product_translation'))
            ->where('locale', config('multilingualism.default_locale'));
    }

    public function gallery()
    {
        if (! $this->variants->first()) {
            return $this->media();
        }

        return $this->variants->first()->media();
    }

    public function getFirstMedia(?string $field = null)
    {
        $this->loadMissing([
            'variants.media.conversions',
        ]);

        if ($this->variants->first()?->media->isNotEmpty()) {
            return $this->variants->first()->getFirstMedia($field);
        }

        return $this->getMedia($field)->first() ?? DefaultMedia::make();
    }

    public function price(): Attribute
    {
        return Attribute::make(
            get: fn () => $this->variants->first()?->pricing()->get()->matched->price,
        );
    }

    public function priceIncludingVat(): Attribute
    {
        return Attribute::make(
            get: fn () => $this->variants->first()?->pricing()->get()->matched->priceIncludingVat,
        );
    }

    protected function makeAllSearchableUsing(Builder $query): Builder
    {
        return $query->with([
            'translations',
            'variants',
            'brand',
            'categories.translations',
            'categories.ancestorsAndSelf.translations',
            'attributeValues.attribute.translations',
        ]);
    }

    public function toSearchableArray(): array
    {
        return [
            'id' => $this->id,
            'price' => $this->price,
            'articleNumbers' => $this->variants->pluck('sku')->toArray(),
            'eans' => $this->variants->pluck('ean')->filter()->toArray(),
            'brand' => $this->brand->name,
            'created_at' => $this->created_at->format('Y-m-d H:i:s'),
            'categories' => $this->categories->pluck('id')->toArray(),
            'order' => $this->order,
            'locales' => Multilingualism::getAvailableLocales()->mapWithKeys(function (string $locale) {
                return [
                    $locale => [
                        'name' => $this->translations->where('locale', $locale)->first()?->name,
                        'description' => $this->translations->where('locale', $locale)->first()?->description,
                    ],
                ];
            })->toArray(),
            ...$this->attributeValues
                ->filter(fn (AttributeValue $attributeValue) => $attributeValue->attribute->filterable)
                ->groupBy('attribute_id')
                ->mapWithKeys(function (Collection $attributeValues) {
                    return [
                        $attributeValues->first()->attribute->key => $attributeValues->pluck('id')->toArray(),
                    ];
                })
                ->toArray(),
        ];
    }

    public function shouldBeSearchable(): bool
    {
        return $this->status === ProductStatus::PUBLISHED;
    }

    public function downloads(): MorphToMany
    {
        return $this->morphToMany(Media::class, 'attachable')
            ->withPivot('properties', 'order')
            ->whereJsonContains('attachables.properties->field', 'downloads')
            ->using(Attachable::class)
            ->orderBy('order')
            ->withTimestamps();
    }

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

    // Relatable contract implementation
    public static function searchForRelation(string $query, array $excludeIds = [], int $limit = 10): Collection
    {
        $queryBuilder = static::query();

        if (! empty($excludeIds)) {
            $queryBuilder->whereNotIn('id', $excludeIds);
        }

        // Search in product name (translations) AND variant SKUs
        $queryBuilder->where(function ($q) use ($query) {
            $q->whereHas('translations', function ($subQ) use ($query) {
                $subQ->where('name', 'LIKE', "%{$query}%");
            })->orWhereHas('variants', function ($subQ) use ($query) {
                $subQ->where('sku', 'LIKE', "%{$query}%");
            });
        });

        // Only include published products
        $queryBuilder->where('status', ProductStatus::PUBLISHED);

        return $queryBuilder->limit($limit)
            ->get()
            ->map(fn ($item) => $item->formatForRelation());
    }

    public static function getForRelation(array $ids): Collection
    {
        return static::whereIn('id', $ids)
            ->get()
            ->map(fn ($item) => $item->formatForRelation());
    }

    public function formatForRelation(): array
    {
        $firstVariant = $this->variants->first();

        return [
            'id' => $this->id,
            'name' => $this->name,
            'type' => 'Product',
            'sku' => $firstVariant?->sku,
            'brand' => $this->brand?->name,
        ];
    }
}
