<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\VariantStockMovement;

class Product extends Model
{
    use HasFactory;
    use ProductStockRecalcConcern;

    public const CHANNEL_BOTH = 'both';
    public const CHANNEL_WEBSITE_ONLY = 'website_only';
    public const CHANNEL_POS_ONLY = 'pos_only';

    public const CHANNEL_VISIBILITIES = [
        self::CHANNEL_BOTH,
        self::CHANNEL_WEBSITE_ONLY,
        self::CHANNEL_POS_ONLY,
    ];

    protected $fillable = [
        'category_id',
        'unit_id',
        'tax_id',
        'tax_rate_snapshot',
        'product_type',
        'parent_id',
        'brand_id',
        'name',
        'slug',
        'sku',
        'tags',
        'barcode',
        'hsn_code',
        'item_code',
        'warranty_months',
        'shipping_days',
        'price',
        'purchase_price',
        'wholesale_price',
        'mrp',
        'opening_stock',
        'current_stock',
        'short_desc',
        'long_desc',
        'image_path',
        'image',
        'is_active',
        'is_verified',
        'channel_visibility',
        'is_featured',
        'is_prebooking',
        'avg_rating',
        'rating_count',
    ];

    protected function casts(): array
    {
        return [
            'price' => 'decimal:2',
            'purchase_price' => 'decimal:2',
            'wholesale_price' => 'decimal:2',
            'mrp' => 'decimal:2',
            'tax_rate_snapshot' => 'decimal:2',
            'opening_stock' => 'integer',
            'current_stock' => 'integer',
            'shipping_days' => 'integer',
            'is_active' => 'boolean',
            'is_verified' => 'boolean',
            'channel_visibility' => 'string',
            'is_featured' => 'boolean',
            'is_prebooking' => 'boolean',
            'avg_rating' => 'decimal:2',
            'rating_count' => 'integer',
            'product_type' => 'string',
            'image' => 'array',
        ];
    }

    /**
     * Cache-busting version for product images (from image JSON or updated_at fallback).
     */
    public function getImageVersion(): ?string
    {
        $raw = $this->attributes['image'] ?? null;
        if ($raw) {
            $paths = is_array($raw) ? $raw : json_decode($raw, true);
            if (is_array($paths) && !empty($paths['version'])) {
                return (string) $paths['version'];
            }
        }

        return $this->updated_at ? (string) $this->updated_at->timestamp : null;
    }

    /**
     * Full URL for image_path with cache-busting version.
     */
    public function getImagePathUrlAttribute(): ?string
    {
        if (!($this->attributes['image_path'] ?? null)) {
            return null;
        }

        return storage_url($this->attributes['image_path'], $this->getImageVersion());
    }

    /**
     * Get the main product image object with full URLs (original, thumb, small, medium, large).
     * Backward compatibility: if image JSON is empty but image_path is set, returns object with all keys equal to image_path URL.
     */
    public function getImageAttribute($value): ?array
    {
        if (is_string($value)) {
            $value = json_decode($value, true);
        }
        $paths = is_array($value) ? $value : null;
        $version = null;
        if (is_array($paths) && !empty($paths['version'])) {
            $version = (string) $paths['version'];
        } else {
            $version = $this->updated_at ? (string) $this->updated_at->timestamp : null;
        }

        if (!empty($paths) && (isset($paths['original']) || isset($paths['thumb']) || isset($paths['small']) || isset($paths['medium']) || isset($paths['large']))) {
            return [
                'original' => isset($paths['original']) ? storage_url($paths['original'], $version) : null,
                'thumb' => isset($paths['thumb']) ? storage_url($paths['thumb'], $version) : null,
                'small' => isset($paths['small']) ? storage_url($paths['small'], $version) : null,
                'medium' => isset($paths['medium']) ? storage_url($paths['medium'], $version) : null,
                'large' => isset($paths['large']) ? storage_url($paths['large'], $version) : null,
            ];
        }
        if ($this->attributes['image_path'] ?? null) {
            $url = storage_url($this->attributes['image_path'], $version);

            return [
                'original' => $url,
                'thumb' => $url,
                'small' => $url,
                'medium' => $url,
                'large' => $url,
            ];
        }

        return null;
    }

    /**
     * Resolve the best image URL for a display context (thumb, small, listing, detail).
     */
    public function getImageUrlForContext(string $context = 'listing'): ?string
    {
        $img = $this->image;

        return self::resolveImageUrlForContext($img, $context);
    }

    /**
     * Pick the first non-empty URL from the context-specific priority chain.
     *
     * @param  array<string, string|null>|null  $img
     */
    public static function resolveImageUrlForContext(?array $img, string $context = 'listing'): ?string
    {
        if (!$img || !is_array($img)) {
            return null;
        }

        $chains = [
            'thumb' => ['thumb', 'small', 'medium', 'large', 'original'],
            'small' => ['small', 'medium', 'thumb', 'large', 'original'],
            'listing' => ['medium', 'small', 'large', 'thumb', 'original'],
            'detail' => ['large', 'medium', 'small', 'original'],
        ];

        $keys = $chains[$context] ?? $chains['listing'];

        foreach ($keys as $key) {
            if (!empty($img[$key])) {
                return $img[$key];
            }
        }

        return null;
    }

    /**
     * Image object for POS: thumb/small only — never includes original.
     *
     * @return array<string, string|null>|null
     */
    public function getPosImageAttribute(): ?array
    {
        $img = $this->image;
        if (!$img) {
            return null;
        }

        return array_filter([
            'thumb' => $img['thumb'] ?? null,
            'small' => $img['small'] ?? null,
        ]) ?: null;
    }

    public function category()
    {
        return $this->belongsTo(Category::class);
    }

    public function unit()
    {
        return $this->belongsTo(Unit::class);
    }

    public function tax()
    {
        return $this->belongsTo(Tax::class);
    }

    public function brand()
    {
        return $this->belongsTo(Brand::class);
    }

    /**
     * Get parent product (for variant products)
     */
    public function parent()
    {
        return $this->belongsTo(Product::class, 'parent_id');
    }

    /**
     * Get variant products (child products for parent)
     */
    public function variants()
    {
        return $this->hasMany(Product::class, 'parent_id');
    }

    /**
     * Get attribute values for this product
     */
    public function attributeValues()
    {
        return $this->belongsToMany(AttributeValue::class, 'product_attribute_values')
            ->withTimestamps();
    }

    public function cartItems()
    {
        return $this->hasMany(CartItem::class);
    }

    public function orderItems()
    {
        return $this->hasMany(OrderItem::class);
    }

    public function stockMovements()
    {
        return $this->hasMany(StockMovement::class);
    }

    /**
     * Get variant stock movements (for unified model, all products can use this)
     */
    public function variantStockMovements()
    {
        return $this->hasMany(VariantStockMovement::class, 'product_id');
    }

    /**
     * Get all stock movements (both types) - for unified model
     */
    public function allStockMovements()
    {
        // For unified model, use VariantStockMovement for all products
        return $this->variantStockMovements();
    }

    public function scopeActive($query)
    {
        return $query->where('is_active', true);
    }

    public function scopeFeatured($query)
    {
        return $query->where('is_featured', true);
    }

    public function scopeForWebsite($query)
    {
        return $query->whereIn('channel_visibility', [
            self::CHANNEL_BOTH,
            self::CHANNEL_WEBSITE_ONLY,
        ]);
    }

    public function scopeForPos($query)
    {
        return $query->whereIn('channel_visibility', [
            self::CHANNEL_BOTH,
            self::CHANNEL_POS_ONLY,
        ]);
    }

    /**
     * In stock for listings:
     * - Prefer sum of available stock at Fulfills-online *stores* when multi-store is on
     * - Fallback: products.current_stock
     * - Variant parent: at least one active website-visible variant in stock
     */
    public function scopeInStock($query)
    {
        $useLocationInventory = $this->locationInventoryEnabled();

        return $query->where(function ($q) use ($useLocationInventory) {
            $q->where(function ($q2) use ($useLocationInventory) {
                $q2->where(function ($q3) {
                    $q3->where('product_type', 'single')
                        ->orWhereNotNull('parent_id');
                });
                if ($useLocationInventory) {
                    $q2->whereHas('inventoryBalances', function ($bq) {
                        $bq->whereHas('location', fn ($lq) => $lq->fulfillsOnline())
                            ->whereRaw('(on_hand - reserved - damaged) > 0');
                    });
                } else {
                    $q2->where('current_stock', '>', 0);
                }
            })->orWhere(function ($q2) use ($useLocationInventory) {
                $q2->whereNull('parent_id')
                    ->where('product_type', 'variant')
                    ->whereHas('variants', function ($vq) use ($useLocationInventory) {
                        $vq->where('is_active', true)
                            ->forWebsite();
                        if ($useLocationInventory) {
                            $vq->whereHas('inventoryBalances', function ($bq) {
                                $bq->whereHas('location', fn ($lq) => $lq->fulfillsOnline())
                                    ->whereRaw('(on_hand - reserved - damaged) > 0');
                            });
                        } else {
                            $vq->where('current_stock', '>', 0);
                        }
                    });
            });
        });
    }

    public function scopeOutOfStock($query)
    {
        return $query->whereNot(function ($q) {
            $this->scopeInStock($q);
        });
    }

    /**
     * Available units for online (sum across Fulfills-online stores only — never warehouse).
     */
    public function onlineAvailableStock(): int
    {
        if ($this->isVariantParent()) {
            return (int) $this->variants()
                ->where('is_active', true)
                ->forWebsite()
                ->get()
                ->sum(fn (self $variant) => $variant->onlineAvailableStock());
        }

        if ($this->locationInventoryEnabled()) {
            return (int) app(\App\Services\LocationInventoryService::class)
                ->getAvailableStock((int) $this->id);
        }

        return max(0, (int) $this->available_stock);
    }

    public function getOnlineAvailableStockAttribute(): int
    {
        return $this->onlineAvailableStock();
    }

    protected function locationInventoryEnabled(): bool
    {
        try {
            return app(\App\Services\LocationInventoryService::class)->useLocationInventory();
        } catch (\Throwable $e) {
            return false;
        }
    }

    public function getIsOutOfStockAttribute(): bool
    {
        if ($this->isVariantParent()) {
            if ($this->relationLoaded('variants') && $this->variants->isNotEmpty()) {
                $activeWebsiteVariants = $this->variants->filter(function ($v) {
                    if (!$v->is_active) {
                        return false;
                    }
                    return in_array($v->channel_visibility, [
                        self::CHANNEL_BOTH,
                        self::CHANNEL_WEBSITE_ONLY,
                    ], true);
                });

                if ($activeWebsiteVariants->isNotEmpty()) {
                    $hasStock = $activeWebsiteVariants->contains(
                        fn ($v) => $v->onlineAvailableStock() > 0
                    );

                    return !$hasStock;
                }
            }

            return !$this->hasActiveWebsiteVariantWithStock();
        }

        return $this->onlineAvailableStock() <= 0;
    }

    /**
     * Whether any active, website-visible variant has sellable stock.
     */
    public function hasActiveWebsiteVariantWithStock(): bool
    {
        return $this->variants()
            ->where('is_active', true)
            ->forWebsite()
            ->get()
            ->contains(fn (self $v) => $v->onlineAvailableStock() > 0);
    }

    public function getDiscountPercentageAttribute(): ?float
    {
        if ($this->mrp && $this->price < $this->mrp) {
            return round((($this->mrp - $this->price) / $this->mrp) * 100, 2);
        }

        return null;
    }


    /**
     * Get available stock (current stock minus reserved stock)
     *
     * @return int
     */
    public function getAvailableStockAttribute(): int
    {
        $reservedStock = $this->stockReservations()
            ->where('status', 'pending')
            ->where('expires_at', '>', now())
            ->sum('quantity');
            
        return max(0, $this->current_stock - $reservedStock);
    }

    /**
     * Stock reservations relationship
     */
    public function stockReservations()
    {
        return $this->hasMany(\App\Models\StockReservation::class);
    }

    public function inventoryBalances()
    {
        return $this->hasMany(InventoryBalance::class);
    }

    public function stockLedgerEntries()
    {
        return $this->hasMany(StockLedger::class);
    }

    /**
     * Check if product has purchase price set
     *
     * @return bool
     */
    public function hasPurchasePrice(): bool
    {
        return !is_null($this->purchase_price) && $this->purchase_price > 0;
    }

    /**
     * Get profit margin percentage
     *
     * @return float|null
     */
    public function getProfitMarginAttribute(): ?float
    {
        if (!$this->hasPurchasePrice() || $this->price <= 0) {
            return null;
        }

        return round((($this->price - $this->purchase_price) / $this->price) * 100, 2);
    }

    /**
     * Get unit profit amount
     *
     * @return float|null
     */
    public function getUnitProfitAttribute(): ?float
    {
        if (!$this->hasPurchasePrice()) {
            return null;
        }

        return round($this->price - $this->purchase_price, 2);
    }

    /**
     * Scope to filter products missing purchase price
     */
    public function scopeMissingPurchasePrice($query)
    {
        return $query->where(function ($q) {
            $q->whereNull('purchase_price')
              ->orWhere('purchase_price', '<=', 0);
        });
    }

    /**
     * Scope to filter products with purchase price
     */
    public function scopeWithPurchasePrice($query)
    {
        return $query->whereNotNull('purchase_price')
                    ->where('purchase_price', '>', 0);
    }

    /**
     * Get all media for this product
     */
    public function media()
    {
        return $this->hasMany(ProductMedia::class)->ordered();
    }

    /**
     * Get only image media
     */
    public function getImagesAttribute()
    {
        if (!$this->relationLoaded('media')) {
            $this->load('media');
        }
        $media = $this->getRelation('media');
        if ($media) {
            return $media->where('type', 'image')->sortBy('display_order')->values();
        }
        return collect();
    }

    /**
     * Get only video media
     */
    public function getVideosAttribute()
    {
        if (!$this->relationLoaded('media')) {
            $this->load('media');
        }
        $media = $this->getRelation('media');
        if ($media) {
            return $media->where('type', 'video')->sortBy('display_order')->values();
        }
        return collect();
    }

    /**
     * Get all reviews for this product.
     */
    public function reviews()
    {
        return $this->hasMany(ProductReview::class);
    }

    /**
     * Get only approved reviews for this product.
     */
    public function approvedReviews()
    {
        return $this->hasMany(ProductReview::class)->where('is_approved', true);
    }

    /**
     * Recalculate average rating and rating count from approved reviews.
     *
     * @return array ['avg_rating' => float, 'rating_count' => int]
     */
    public function recalculateRating(): array
    {
        $approvedReviews = $this->reviews()->approved()->get();

        if ($approvedReviews->isEmpty()) {
            $avgRating = 0.00;
            $ratingCount = 0;
        } else {
            $ratingCount = $approvedReviews->count();
            $avgRating = round($approvedReviews->avg('rating'), 2);
        }

        $this->update([
            'avg_rating' => $avgRating,
            'rating_count' => $ratingCount,
        ]);

        return [
            'avg_rating' => $avgRating,
            'rating_count' => $ratingCount,
        ];
    }

    /**
     * Scope a query to eager load rating summary.
     */
    public function scopeWithRatings($query)
    {
        return $query->withCount([
            'reviews as approved_reviews_count' => function ($query) {
                $query->where('is_approved', true);
            }
        ])->withAvg([
            'reviews as avg_rating_calculated' => function ($query) {
                $query->where('is_approved', true);
            }
        ], 'rating');
    }

    /**
     * Scope for single products (no parent, product_type = 'single')
     */
    public function scopeSingle($query)
    {
        return $query->whereNull('parent_id')
                    ->where('product_type', 'single');
    }

    /**
     * Scope for variant child products (has parent_id)
     */
    public function scopeVariant($query)
    {
        return $query->whereNotNull('parent_id');
    }

    /**
     * Scope for variant parent products (no parent, product_type = 'variant')
     */
    public function scopeVariantParent($query)
    {
        return $query->whereNull('parent_id')
                    ->where('product_type', 'variant');
    }

    /**
     * Check if this is a single product
     */
    public function isSingle(): bool
    {
        return is_null($this->parent_id) && $this->product_type === 'single';
    }

    /**
     * Check if this is a variant child product
     */
    public function isVariant(): bool
    {
        return !is_null($this->parent_id);
    }

    /**
     * Check if this is a variant parent product
     */
    public function isVariantParent(): bool
    {
        return is_null($this->parent_id) && $this->product_type === 'variant';
    }

    /**
     * Get variation name from attribute values (for variant products)
     */
    public function getVariationNameAttribute(): string
    {
        if (!$this->parent_id) {
            // Not a variant, return empty or product name
            return $this->name;
        }
        
        $attributeValues = $this->attributeValues;
        if ($attributeValues->isEmpty()) {
            return $this->sku;
        }
        
        return $attributeValues->map(function ($value) {
            return $value->attribute->name . ': ' . $value->value;
        })->implode(' - ');
    }
}
