<?php

namespace App\Services;

use App\Models\Product;
use App\Models\ProductVariant;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

class TorobCatalogService
{
    public const PER_PAGE = 100;

    public const API_VERSION = 'torob_api_v3';

    /**
     * Paginated catalog for Torob (exactly 100 items per page except last).
     *
     * @return array{api_version:string,current_page:int,total:int,max_pages:int,products:array}
     */
    public function paginate(int $page, string $sort): array
    {
        $base = $this->eligibleVariantsQuery();

        if ($sort === 'date_updated_desc') {
            $base->orderByDesc('product_variants.updated_at')->orderByDesc('product_variants.id');
        } else {
            $base->orderByDesc('product_variants.created_at')->orderByDesc('product_variants.id');
        }

        $total = (clone $base)->count();
        $maxPages = max(1, (int) ceil($total / self::PER_PAGE));
        $page = max(1, $page);

        $variants = $base
            ->with($this->productEagerLoads())
            ->skip(($page - 1) * self::PER_PAGE)
            ->take(self::PER_PAGE)
            ->get();

        return $this->wrap($page, $total, $maxPages, $variants);
    }

    /**
     * Lookup by stable page_unique values. Missing items are omitted.
     *
     * @param  list<string>  $uniques
     */
    public function byUniques(array $uniques): array
    {
        $parsed = [];
        foreach ($uniques as $unique) {
            $unique = trim((string) $unique);
            if (!preg_match('/^(\d+)_(\d+)$/', $unique, $m)) {
                continue;
            }
            $parsed[] = [(int) $m[1], (int) $m[2]];
        }

        if ($parsed === []) {
            return $this->wrap(1, 0, 1, collect());
        }

        $variantIds = array_values(array_unique(array_column($parsed, 1)));

        $variants = $this->eligibleVariantsQuery()
            ->whereIn('product_variants.id', $variantIds)
            ->with($this->productEagerLoads())
            ->get()
            ->keyBy('id');

        $ordered = collect();
        foreach ($parsed as [$productId, $variantId]) {
            $v = $variants->get($variantId);
            if ($v && (int) $v->product_id === $productId) {
                $ordered->push($v);
            }
        }

        // De-dupe while preserving request order
        $ordered = $ordered->unique('id')->values();

        return $this->wrap(1, $ordered->count(), 1, $ordered);
    }

    /**
     * Lookup by absolute product URLs. Missing/deleted items omitted.
     *
     * @param  list<string>  $urls
     */
    public function byUrls(array $urls): array
    {
        $ordered = collect();

        foreach ($urls as $url) {
            $variant = $this->resolveVariantFromUrl((string) $url);
            if ($variant) {
                $ordered->push($variant);
            }
        }

        $ordered = $ordered->unique('id')->values();

        return $this->wrap(1, $ordered->count(), 1, $ordered);
    }

    public function pageUnique(ProductVariant $variant): string
    {
        return $variant->product_id . '_' . $variant->id;
    }

    public function pageUrl(ProductVariant $variant): string
    {
        $product = $variant->product;
        $url = route('product.show', $product->slug, absolute: true);

        // Always include v= so Torob can round-trip the exact variant
        $url = $url . (str_contains($url, '?') ? '&' : '?') . 'v=' . $variant->id;

        return $this->forceHttpsAbsolute($url);
    }

    /**
     * @param  Collection<int, ProductVariant>  $variants
     */
    public function wrap(int $currentPage, int $total, int $maxPages, Collection $variants): array
    {
        return [
            'api_version'  => self::API_VERSION,
            'current_page' => $currentPage,
            'total'        => $total,
            'max_pages'    => $maxPages,
            'products'     => $variants->map(fn (ProductVariant $v) => $this->mapVariant($v))->values()->all(),
        ];
    }

    public function mapVariant(ProductVariant $variant): array
    {
        $product = $variant->product;

        $priceToman = (int) round((float) $variant->price);
        $discountToman = $variant->discount_price !== null
            ? (int) round((float) $variant->discount_price)
            : null;

        $hasDiscount = $discountToman !== null && $discountToman > 0 && $discountToman < $priceToman;
        $currentToman = $hasDiscount ? $discountToman : $priceToman;

        // Torob Product API v3 expects prices in Toman (integer, never null).
        // Unavailable products must report current_price = 0.
        $availability = (int) $variant->stock > 0;
        $currentPrice = $availability ? max(0, $currentToman) : 0;
        $oldPrice = ($availability && $hasDiscount) ? $priceToman : null;

        $title = (string) $product->title;
        if ($variant->name) {
            $title = $title . ' - ' . $variant->name;
        }
        $title = Str::limit($title, 500, '');

        $subtitle = $product->title_en ? Str::limit((string) $product->title_en, 500, '') : null;

        $categoryName = optional($product->categories->first())->name;
        if ($categoryName) {
            $categoryName = Str::limit((string) $categoryName, 200, '');
        }

        $dateAdded = $this->iso($variant->created_at ?? $product->created_at);
        $dateUpdated = $this->iso($variant->updated_at ?? $product->updated_at ?? $variant->created_at);
        if ($dateUpdated === null) {
            $dateUpdated = $dateAdded;
        }

        return [
            'page_unique'      => $this->pageUnique($variant),
            'page_url'         => Str::limit($this->pageUrl($variant), 1500, ''),
            'product_group_id' => (string) $product->id,
            'title'            => $title,
            'subtitle'         => $subtitle,
            'current_price'    => $currentPrice,
            'old_price'        => $oldPrice,
            'availability'     => $availability,
            'category_name'    => $categoryName,
            'image_links'      => $this->imageLinks($variant),
            // Must be a JSON object {}. Empty PHP [] encodes as [] and Torob rejects it.
            'spec'             => (object) $this->spec($variant),
            'guarantee'        => $product->warranty
                ? Str::limit((string) $product->warranty, 200, '')
                : null,
            'short_desc'       => $this->shortDesc($product),
            'date_added'       => $dateAdded,
            'date_updated'     => $dateUpdated,
        ];
    }

    private function eligibleVariantsQuery()
    {
        return ProductVariant::query()
            ->select('product_variants.*')
            ->join('products', 'products.id', '=', 'product_variants.product_id')
            ->where('products.is_active', true)
            ->where('product_variants.is_active', true);
    }

    private function resolveVariantFromUrl(string $url): ?ProductVariant
    {
        $url = trim($url);
        if ($url === '') {
            return null;
        }

        $parts = parse_url($url);
        if ($parts === false || empty($parts['path'])) {
            return null;
        }

        $path = rawurldecode($parts['path']);
        $path = '/' . trim($path, '/');

        // /product/{slug}
        if (!preg_match('#/product/([^/]+)/?$#u', $path, $m)) {
            return null;
        }
        $slug = $m[1];

        $variantId = null;
        if (!empty($parts['query'])) {
            parse_str($parts['query'], $query);
            if (isset($query['v']) && ctype_digit((string) $query['v'])) {
                $variantId = (int) $query['v'];
            }
        }

        $product = Product::query()
            ->where('is_active', true)
            ->where('slug', $slug)
            ->with([
                'categories',
                'images',
                'attributes' => fn ($q) => $q->orderBy('sort_order')->orderBy('id'),
                'variants' => fn ($q) => $q->where('is_active', true),
            ])
            ->first();

        if (!$product) {
            return null;
        }

        if ($variantId) {
            $variant = $product->variants->firstWhere('id', $variantId);
            if ($variant) {
                $variant->setRelation('product', $product);

                return $variant;
            }

            return null;
        }

        $variant = $product->variants->firstWhere('is_default', true)
            ?? $product->variants->first();

        if ($variant) {
            $variant->setRelation('product', $product);
        }

        return $variant;
    }

    /**
     * @return list<string>
     */
    private function imageLinks(ProductVariant $variant): array
    {
        $product = $variant->product;
        $links = [];

        if ($variant->image) {
            $links[] = asset('uploads/products/variants/' . $variant->image);
        }

        if ($product->featured_image) {
            $links[] = asset('uploads/products/' . $product->featured_image);
        }

        foreach ($product->images as $img) {
            if ($img->image) {
                $links[] = asset('uploads/products/' . $img->image);
            }
        }

        $links = array_values(array_unique(array_filter($links)));
        $links = array_map(
            fn ($u) => Str::limit($this->forceHttpsAbsolute((string) $u), 1000, ''),
            $links
        );

        return $links;
    }

    private function forceHttpsAbsolute(string $url): string
    {
        if (str_starts_with($url, 'http://')) {
            return 'https://' . substr($url, 7);
        }

        if (str_starts_with($url, '//')) {
            return 'https:' . $url;
        }

        if (str_starts_with($url, '/')) {
            return rtrim((string) config('app.url'), '/') . $url;
        }

        return $url;
    }

    /**
     * Torob requires dict[str, str|int] from product attributes (ویژگی‌های محصول).
     * Empty specs => {} (encoded via (object) in mapVariant).
     *
     * @return array<string, string|int>
     */
    private function spec(ProductVariant $variant): array
    {
        $spec = [];
        $product = $variant->product;

        $attributes = $product->relationLoaded('attributes')
            ? $product->attributes
            : $product->attributes()->orderBy('sort_order')->orderBy('id')->get();

        foreach ($attributes as $attr) {
            $name = trim((string) $attr->name);
            if ($name === '') {
                continue;
            }

            $value = null;

            // For the attribute that defines this variant option, use the selected option
            if (
                $variant->attribute_id
                && (int) $variant->attribute_id === (int) $attr->id
                && filled($variant->name)
            ) {
                $value = trim((string) $variant->name);
            } else {
                $values = array_values(array_map('strval', $attr->valuesList()));
                if (count($values) === 1) {
                    $value = $values[0];
                } elseif (count($values) > 1) {
                    $value = implode('، ', $values);
                }
            }

            if ($value === null || $value === '') {
                continue;
            }

            $spec[$name] = $value;
        }

        return $spec;
    }

    /**
     * @return array<string, mixed>
     */
    private function productEagerLoads(): array
    {
        return [
            'product.categories',
            'product.images',
            'product.attributes' => fn ($q) => $q->orderBy('sort_order')->orderBy('id'),
        ];
    }

    private function shortDesc(Product $product): ?string
    {
        $raw = $product->features ?: $product->description;
        if (!$raw) {
            return null;
        }
        $text = trim(preg_replace('/\s+/u', ' ', strip_tags((string) $raw)) ?? '');
        if ($text === '') {
            return null;
        }

        return Str::limit($text, 500, '');
    }

    private function iso($dt): ?string
    {
        if (!$dt) {
            return null;
        }
        $carbon = $dt instanceof Carbon ? $dt->copy() : Carbon::parse($dt);

        return $carbon->timezone('Asia/Tehran')->format('Y-m-d\TH:i:sP');
    }
}
