<?php

namespace App\Domains\Catalog\Models;

use App\Domains\Attachable\Models\Attachment;
use App\Domains\Catalog\Models\Brand;
use App\Domains\Catalog\Models\Category;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;

#[Fillable([
    'tenant_codigo',
    'categoria_id',
    'brand_id',
    'slug',
    'nombre',
    'descripcion',
    'precio',
])]
class Product extends Model
{
    use HasFactory;

    protected $table = 'productos';

    protected function casts(): array
    {
        return [
            'categoria_id' => 'integer',
            'brand_id' => 'integer',
            'precio' => 'decimal:2',
        ];
    }

    /**
     * @return BelongsTo<Tenant, $this>
     */
    public function tenant(): BelongsTo
    {
        return $this->belongsTo(Tenant::class, 'tenant_codigo', 'codigo');
    }

    /**
     * @return BelongsTo<Category, $this>
     */
    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class, 'categoria_id');
    }

    /**
     * @return BelongsTo<Brand, $this>
     */
    public function brand(): BelongsTo
    {
        return $this->belongsTo(Brand::class, 'brand_id');
    }

    /**
     * @return HasMany<ProductVariant, $this>
     */
    public function variants(): HasMany
    {
        return $this->hasMany(ProductVariant::class, 'producto_id');
    }

    /**
     * @return BelongsToMany<Attribute, $this>
     */
    public function attributes(): BelongsToMany
    {
        return $this->belongsToMany(
            Attribute::class,
            'products_attributes',
            'product_id',
            'attribute_id'
        )->withTimestamps();
    }

    /**
     * @return BelongsToMany<Attachment, $this>
     */
    public function attachments(): BelongsToMany
    {
        return $this->belongsToMany(
            Attachment::class,
            'productos_attachments',
            'producto_id',
            'attachment_id'
        )->withTimestamps();
    }

    /**
     * Create a variant for this product with its definitions.
     *
     * @param array<string, mixed> $data
     */
    public function createVariant(array $data): ProductVariant
    {
        $definitions = $data['definitions'] ?? [];
        $this->validateVariantDefinitions($definitions);

        unset($data['definitions']);

        /** @var ProductVariant $variant */
        $variant = $this->variants()->create($data);
        $variant->definitions()->createMany($definitions);

        return $variant;
    }

    /**
     * Create multiple variants for this product.
     *
     * @param array<int, array<string, mixed>> $variantsData
     * @return \Illuminate\Database\Eloquent\Collection<int, ProductVariant>
     */
    public function createVariants(array $variantsData): \Illuminate\Database\Eloquent\Collection
    {
        $variants = new \Illuminate\Database\Eloquent\Collection();

        foreach ($variantsData as $variantData) {
            $variants->push($this->createVariant($variantData));
        }

        return $variants;
    }

    /**
     * Update a product variant.
     *
     * @param array<string, mixed> $data
     */
    public function updateVariant(ProductVariant $variant, array $data): ProductVariant
    {
        $definitions = $data['definitions'] ?? [];
        $this->validateVariantDefinitions($definitions);

        unset($data['definitions']);
        unset($data['producto_id']);

        $variant->update($data);
        $variant->definitions()->delete();
        $variant->definitions()->createMany($definitions);

        return $variant;
    }

    /**
     * Delete a product variant.
     */
    public function deleteVariant(ProductVariant $variant): void
    {
        $variant->definitions()->delete();
        $variant->delete();
    }

    /**
     * Validate variant definitions options against Attribute configuration.
     *
     * @param array $definitions
     * @throws \InvalidArgumentException
     */
    protected function validateVariantDefinitions(array $definitions): void
    {
        foreach ($definitions as $definition) {
            $attributeId = $definition['attribute_id'] ?? null;
            if (!$attributeId) {
                continue;
            }

            $attribute = Attribute::find($attributeId);
            if (!$attribute) {
                throw new \InvalidArgumentException("Attribute with ID {$attributeId} not found.");
            }

            if ($attribute->type === \App\Domains\Shared\Enums\FieldType::Select) {
                $allowedValues = $attribute->options()->pluck('value')->toArray();
                $val = $definition['value'] ?? null;
                if ($val !== null && !in_array($val, $allowedValues, true)) {
                    throw new \InvalidArgumentException("The value '{$val}' is not a valid option for the select attribute '{$attribute->nombre}'.");
                }
            } elseif ($attribute->type === \App\Domains\Shared\Enums\FieldType::Multiselect) {
                $allowedValues = $attribute->options()->pluck('value')->toArray();
                $val = $definition['value'] ?? null;
                if ($val !== null) {
                    $values = [];
                    if (is_array($val)) {
                        $values = $val;
                    } else {
                        $decoded = json_decode($val, true);
                        if (is_array($decoded)) {
                            $values = $decoded;
                        } else {
                            $values = array_map('trim', explode(',', $val));
                        }
                    }
                    foreach ($values as $v) {
                        if (!in_array($v, $allowedValues, true)) {
                            throw new \InvalidArgumentException("The value '{$v}' is not a valid option for the multiselect attribute '{$attribute->nombre}'.");
                        }
                    }
                }
            }
        }
    }

    /**
     * Create an attribute.
     *
     * @param array<string, mixed> $data
     */
    public static function createAttribute(Tenant $tenant, array $data): Attribute
    {
        $options = $data['options'] ?? [];
        unset($data['options']);

        $type = \App\Domains\Shared\Enums\FieldType::from((string) $data['type']);
        if (! $type->supportsOptions() && ! empty($options)) {
            throw new \InvalidArgumentException('Options are only allowed for select and multiselect attributes.');
        }

        if (! $type->supportsOptions()) {
            $data['metadata_schema'] = null;
        }

        /** @var Attribute $attribute */
        $attribute = Attribute::query()->create([
            ...$data,
            'tenant_codigo' => $tenant->codigo,
        ]);
        $attribute->options()->createMany($options);

        return $attribute->load('options');
    }

    /**
     * Update an attribute.
     *
     * @param array<string, mixed> $data
     */
    public static function updateAttribute(Attribute $attribute, array $data): Attribute
    {
        $options = $data['options'] ?? [];
        unset($data['options']);

        $typeStr = $data['type'] ?? $attribute->type->value;
        $type = \App\Domains\Shared\Enums\FieldType::from((string) $typeStr);
        if (! $type->supportsOptions() && ! empty($options)) {
            throw new \InvalidArgumentException('Options are only allowed for select and multiselect attributes.');
        }

        if (! $type->supportsOptions()) {
            $data['metadata_schema'] = null;
        }

        $attribute->update($data);
        $attribute->options()->delete();
        $attribute->options()->createMany($options);

        return $attribute->load('options');
    }

    /**
     * Delete an attribute.
     */
    public static function deleteAttribute(Attribute $attribute): void
    {
        $attribute->options()->delete();
        $attribute->delete();
    }
}
