<?php

namespace App\Http\Controllers;

use App\Models\Item;
use App\Scopes\ZoneScope;
use App\Scopes\StoreScope;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;

class MetaCatalogFeedController extends Controller
{
    private const CACHE_KEY = 'meta_catalog_feed_xml';
    private const LABEL_CACHE_KEY = 'meta_catalog_labels_csv';
    private const CACHE_TTL  = 3600; // 1 hour
    private const TEST_PRODUCT_KEYWORDS = [
        'demo',
        'test',
        'dummy',
        'sample',
        'placeholder',
    ];

    public function xml()
    {
        $xml = Cache::remember(self::CACHE_KEY, self::CACHE_TTL, function () {
            return $this->buildXml();
        });

        return response($xml, 200)
            ->header('Content-Type', 'application/xml; charset=UTF-8')
            ->header('Cache-Control', 'public, max-age=3600');
    }

    // Force rebuild — useful after bulk imports or manual trigger
    public function refresh()
    {
        Cache::forget(self::CACHE_KEY);
        $xml = $this->buildXml();
        Cache::put(self::CACHE_KEY, $xml, self::CACHE_TTL);

        return response($xml, 200)
            ->header('Content-Type', 'application/xml; charset=UTF-8');
    }

    public function labelsCsv()
    {
        $csv = Cache::remember(self::LABEL_CACHE_KEY, self::CACHE_TTL, function () {
            return $this->buildLabelsCsv();
        });

        return response($csv, 200)
            ->header('Content-Type', 'text/csv; charset=UTF-8')
            ->header('Content-Disposition', 'inline; filename="meta-catalog-labels.csv"')
            ->header('Cache-Control', 'public, max-age=3600');
    }

    // ─────────────────────────────────────────────────────────────────────
    private function buildXml(): string
    {
        $frontendUrl = rtrim((string) config('app.front_end_url'), '/');
        $businessName = $this->getBusinessName();

        $doc = new \DOMDocument('1.0', 'UTF-8');
        $doc->formatOutput = false; // smaller output for faster Facebook parsing

        $rss = $doc->createElement('rss');
        $rss->setAttribute('version', '2.0');
        $rss->setAttribute('xmlns:g', 'http://base.google.com/ns/1.0');
        $doc->appendChild($rss);

        $channel = $doc->createElement('channel');
        $rss->appendChild($channel);

        $this->addText($doc, $channel, 'title', $businessName . ' Product Feed');
        $this->addText($doc, $channel, 'link', $frontendUrl);
        $this->addText($doc, $channel, 'description', 'All active products from ' . $businessName);

        // Chunk through live catalog products only; hidden/test/seed rows must not reach Meta.
        $query = Item::withoutGlobalScope(ZoneScope::class)
            ->withoutGlobalScope(StoreScope::class)
            ->withoutGlobalScope('storage')
            ->where('status', 1)
            ->where('is_approved', 1)
            ->whereNotNull('name')
            ->where('name', '!=', '')
            ->whereNotNull('slug')
            ->where('slug', '!=', '')
            ->whereNotNull('image')
            ->where('image', '!=', '')
            ->where('price', '>', 0)
            ->whereHas('store', function ($query) {
                $query->where('status', 1)
                    ->where(function ($query) {
                        $query->where('store_business_model', 'commission')
                            ->orWhereHas('store_sub', function ($query) {
                                $query->where(function ($query) {
                                    $query->where('max_order', 'unlimited')->orWhere('max_order', '>', 0);
                                });
                            });
                    });
            })
            ->whereHas('module', function ($query) {
                $query->where('status', 1)
                    ->whereNotIn('module_type', ['parcel', 'rental']);
            })
            ->with([
                'category:id,name',
                'ecommerce_item_details.brand:id,name',
                'storage',
            ])
            ->select([
                'id', 'name', 'slug', 'description', 'price', 'discount',
                'discount_type', 'stock', 'image', 'images', 'category_id',
                'store_id', 'module_id', 'free_delivery_badge_status',
                'status', 'is_approved', 'updated_at',
            ])
            ->orderBy('id');

        foreach ([
            'meta_ad_priority',
            'meta_ads_high_profit',
            'meta_ads_excluded',
        ] as $column) {
            if (Schema::hasColumn('items', $column)) {
                $query->addSelect($column);
            }
        }

        $this->applyOptionalVisibilityGuards($query);
        $this->applyTestProductGuards($query);

        $query
            ->chunk(200, function ($items) use ($doc, $channel, $frontendUrl) {
                foreach ($items as $item) {
                    try {
                        if (!$this->shouldExportItem($item)) {
                            continue;
                        }

                        $this->appendItem($doc, $channel, $item, $frontendUrl);
                    } catch (\Throwable $e) {
                        Log::warning('MetaCatalogFeed: skipped item ' . $item->id . ' — ' . $e->getMessage());
                    }
                }
            });

        return $doc->saveXML();
    }

    private function buildLabelsCsv(): string
    {
        $stream = fopen('php://temp', 'w+');
        if ($stream === false) {
            throw new \RuntimeException('Unable to open the Meta catalog label stream.');
        }

        fputcsv($stream, ['id', 'custom_label_3', 'custom_label_4']);

        $columns = ['id', 'discount', 'discount_type', 'price'];
        foreach ([
            'meta_ad_priority',
            'meta_ads_high_profit',
            'meta_ads_excluded',
        ] as $column) {
            if (Schema::hasColumn('items', $column)) {
                $columns[] = $column;
            }
        }

        $query = Item::withoutGlobalScope(ZoneScope::class)
            ->withoutGlobalScope(StoreScope::class)
            ->withoutGlobalScope('storage')
            ->withoutGlobalScope('translate')
            ->without(['translations', 'storage'])
            ->select($columns)
            ->orderBy('id');
        $this->applyOptionalVisibilityGuards($query);

        $query->chunkById(500, function ($items) use ($stream) {
            foreach ($items as $item) {
                $regularPrice = max(0, (float) $item->price);
                $salePrice = $this->calcSalePrice($item);
                fputcsv($stream, [
                    (string) $item->id,
                    $this->priorityLabel($item),
                    $this->performanceLabel($item, $salePrice, $regularPrice),
                ]);
            }
        });

        rewind($stream);
        $csv = stream_get_contents($stream);
        fclose($stream);

        if ($csv === false) {
            throw new \RuntimeException('Unable to read the Meta catalog label stream.');
        }

        return $csv;
    }

    private function appendItem(\DOMDocument $doc, \DOMElement $channel, Item $item, string $frontendUrl): void
    {
        $mainImage = $item->image_full_url ?? '';
        if (!$this->isValidCatalogImage($mainImage)) return; // Facebook requires valid, real product image

        $node = $doc->createElement('item');
        $channel->appendChild($node);

        // ── IDs ────────────────────────────────────────────────────────
        $this->addG($doc, $node, 'id',            (string) $item->id);
        $this->addG($doc, $node, 'item_group_id', (string) $item->id);

        // ── Basic info ─────────────────────────────────────────────────
        $title = $this->fixTitleCase($this->cleanText($item->name, 65));
        $this->addG($doc, $node, 'title', $title);
        $this->addG($doc, $node, 'description', $this->cleanText($item->description ?: $item->name, 5000));

        // ── Links ──────────────────────────────────────────────────────
        $slug      = ($item->slug && $item->slug !== 'null' && $item->slug !== 'undefined')
            ? $item->slug : $item->id;
        $productUrl = $frontendUrl . '/product/' . $slug;
        $this->addG($doc, $node, 'link', $productUrl);

        // ── Images ────────────────────────────────────────────────────
        $this->addG($doc, $node, 'image_link', $mainImage);

        $additionalImages = array_values(array_filter(
            $item->images_full_url ?? [],
            fn($url) => $url !== $mainImage && $this->isValidCatalogImage($url)
        ));
        foreach (array_slice($additionalImages, 0, 10) as $imgUrl) {
            $this->addG($doc, $node, 'additional_image_link', $imgUrl);
        }

        // ── Pricing ───────────────────────────────────────────────────
        $regularPrice = (float) $item->price;
        $salePrice    = $this->calcSalePrice($item);
        $this->addG($doc, $node, 'price',      number_format($regularPrice, 2, '.', '') . ' BDT');
        if ($salePrice < $regularPrice) {
            $this->addG($doc, $node, 'sale_price', number_format($salePrice, 2, '.', '') . ' BDT');
        }

        // ── Availability & Quantity ───────────────────────────────────
        // Items in this feed are active/approved; stock=0 means untracked — default to 1
        $stockQty     = (int) $item->stock;
        $availability = $stockQty > 0 ? 'in stock' : 'in stock'; // active items always in stock
        $quantity     = max(1, $stockQty); // Facebook Shops requires quantity >= 1
        $this->addG($doc, $node, 'availability', $availability);
        $this->addG($doc, $node, 'quantity',     (string) $quantity);

        // ── Condition ─────────────────────────────────────────────────
        $this->addG($doc, $node, 'condition', 'new');

        // ── Category / Brand ──────────────────────────────────────────
        $categoryName = $item->category->name ?? '';
        $brandName    = $item->ecommerce_item_details->brand->name ?? '';

        if ($categoryName) {
            $this->addG($doc, $node, 'product_type', $categoryName);
        }
        if ($brandName) {
            $this->addG($doc, $node, 'brand', $brandName);
        } else {
            $this->addG($doc, $node, 'brand', 'Ammar'); // Facebook requires brand
        }

        // ── Custom labels (for Product Sets in Commerce Manager) ───────
        // custom_label_0 → free delivery
        // custom_label_1 → category
        // custom_label_2 → brand
        // custom_label_3 -> ad priority / Best Seller test set
        // custom_label_4 -> exclusion / High Profit / discount segment
        // Products remain in the feed so existing All Products ads are unaffected.
        $this->addG($doc, $node, 'custom_label_0', $item->free_delivery_badge_status ? 'free_delivery' : 'paid_delivery');
        $this->addG($doc, $node, 'custom_label_1', $this->cleanText($categoryName, 100) ?: 'Uncategorized');
        $this->addG($doc, $node, 'custom_label_2', $this->cleanText($brandName, 100)   ?: 'No Brand');
        $this->addG($doc, $node, 'custom_label_3', $this->priorityLabel($item));
        $this->addG($doc, $node, 'custom_label_4', $this->performanceLabel($item, $salePrice, $regularPrice));
    }

    // ─── Helpers ─────────────────────────────────────────────────────────

    private function calcSalePrice(Item $item): float
    {
        $price    = (float) $item->price;
        $discount = (float) $item->discount;

        if ($discount <= 0) return $price;

        return ($item->discount_type === 'percent')
            ? max(0, $price - ($price * $discount / 100))
            : max(0, $price - $discount);
    }

    private function priorityLabel(Item $item): string
    {
        return match (strtolower((string) ($item->meta_ad_priority ?: 'medium'))) {
            'high' => 'best_seller',
            'low' => 'priority_low',
            default => 'priority_medium',
        };
    }

    private function performanceLabel(Item $item, float $salePrice, float $regularPrice): string
    {
        if ((bool) ($item->meta_ads_excluded ?? false)) {
            return 'meta_excluded';
        }

        if ((bool) ($item->meta_ads_high_profit ?? false)) {
            return 'high_profit';
        }

        return $salePrice < $regularPrice ? 'on_offer' : 'regular_price';
    }

    private function cleanText(?string $text, int $maxLen): string
    {
        if (!$text) return '';
        $text = strip_tags(html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
        $text = preg_replace('/\s+/', ' ', $text);
        $text = trim($text);
        return mb_substr($text, 0, $maxLen);
    }

    // Convert all-uppercase titles to title case (Meta recommendation)
    private function fixTitleCase(string $text): string
    {
        if (strlen($text) < 4) return $text;
        $letters     = preg_replace('/[^a-zA-Z]/', '', $text);
        $upperCount  = strlen(preg_replace('/[^A-Z]/', '', $letters));
        $letterCount = strlen($letters);
        if ($letterCount > 0 && ($upperCount / $letterCount) > 0.7) {
            return ucwords(strtolower($text));
        }
        return $text;
    }

    private function isValidUrl(?string $url): bool
    {
        return $url && filter_var($url, FILTER_VALIDATE_URL) !== false
            && preg_match('/\.(jpg|jpeg|png|gif|webp)(\?.*)?$/i', $url);
    }

    private function isValidCatalogImage(?string $url): bool
    {
        if (!$this->isValidUrl($url)) {
            return false;
        }

        $lowerUrl = strtolower($url);
        $placeholderPaths = [
            '/assets/admin/img/',
            '/placeholder',
            '/dummy',
        ];

        foreach ($placeholderPaths as $path) {
            if (str_contains($lowerUrl, $path)) {
                return false;
            }
        }

        return true;
    }

    private function shouldExportItem(Item $item): bool
    {
        if ((int) $item->status !== 1 || (int) $item->is_approved !== 1) {
            return false;
        }

        if ((float) $item->price <= 0) {
            return false;
        }

        if (!$item->store_id || !$item->module_id) {
            return false;
        }

        if ($this->looksLikeTestProduct($item)) {
            return false;
        }

        return $this->isValidCatalogImage($item->image_full_url ?? '');
    }

    private function looksLikeTestProduct(Item $item): bool
    {
        $haystack = strtolower(trim(($item->name ?? '') . ' ' . ($item->slug ?? '')));

        foreach (self::TEST_PRODUCT_KEYWORDS as $keyword) {
            if (preg_match('/(^|[^a-z0-9])' . preg_quote($keyword, '/') . '([^a-z0-9]|$)/i', $haystack)) {
                return true;
            }
        }

        return false;
    }

    private function applyTestProductGuards($query): void
    {
        $pattern = '(^|[^a-z0-9])(' . implode('|', array_map('preg_quote', self::TEST_PRODUCT_KEYWORDS)) . ')([^a-z0-9]|$)';

        $query->whereRaw(
            "LOWER(CONCAT(COALESCE(name, ''), ' ', COALESCE(slug, ''))) NOT REGEXP ?",
            [$pattern]
        );
    }

    private function applyOptionalVisibilityGuards($query): void
    {
        $table = (new Item())->getTable();

        if (Schema::hasColumn($table, 'deleted_at')) {
            $query->whereNull('deleted_at');
        }

        foreach (['is_published', 'published', 'is_visible', 'visible', 'is_active'] as $column) {
            if (Schema::hasColumn($table, $column)) {
                $query->where($column, 1);
            }
        }

        foreach (['is_hidden', 'hidden'] as $column) {
            if (Schema::hasColumn($table, $column)) {
                $query->where($column, 0);
            }
        }
    }

    private function addG(\DOMDocument $doc, \DOMElement $parent, string $tag, string $value): void
    {
        $el = $doc->createElement('g:' . $tag);
        $el->appendChild($doc->createCDATASection($value));
        $parent->appendChild($el);
    }

    private function addText(\DOMDocument $doc, \DOMElement $parent, string $tag, string $value): void
    {
        $el = $doc->createElement($tag);
        $el->appendChild($doc->createTextNode($value));
        $parent->appendChild($el);
    }

    private function getBusinessName(): string
    {
        try {
            $setting = \App\Models\BusinessSetting::where('key', 'business_name')->first();
            return $setting?->value ?: 'Ammar';
        } catch (\Throwable) {
            return 'Ammar';
        }
    }
}
