<?php

namespace App\Services\AI;

use App\Contracts\CatalogPageDataResolver;
use App\DTOs\CatalogPageData;
use App\DTOs\CatalogProduct;
use App\Services\ProductPageScraper;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Log;

use function Laravel\Ai\agent;

class LaravelAiCatalogPageDataResolver implements CatalogPageDataResolver
{
    public function __construct(private ProductPageScraper $scraper) {}

    /**
     * @param  list<string>  $productUrls
     */
    public function resolve(
        string $brandName,
        array $productUrls,
        ?string $brandUrl = null,
        ?string $notes = null,
        ?string $advertiserId = null,
        ?string $campaignId = null,
    ): CatalogPageData {
        $sections = [];
        $productImages = [];

        foreach ($productUrls as $url) {
            $html = $this->scraper->fetchHtml($url);
            $image = $this->scraper->primaryImageUrl($html, $url);
            $productImages[] = $image;
            $imageLine = filled($image)
                ? "Primary image URL: {$image}\n"
                : "Primary image URL: none found, do not guess one for this product.\n";
            $sections[] = "Product URL: {$url}\n{$imageLine}Page text:\n".$this->scraper->pageText($html);
        }

        if ($brandUrl) {
            $html = $this->scraper->fetchHtml($brandUrl);
            $sections[] = "Brand URL: {$brandUrl}\nBrand site text:\n".$this->scraper->pageText($html);
        }

        $prompt = "Promoting brand: {$brandName}\n";

        if ($advertiserId) {
            $prompt .= "Advertiser ID: {$advertiserId}\n";
        }

        if ($campaignId) {
            $prompt .= "Campaign ID: {$campaignId}\n";
        }

        if ($notes) {
            $prompt .= "Brand notes: {$notes}\n";
        }

        $prompt .= "\nBuild one editorial catalog JSON for these product URLs, in the same order. Each URL is a distinct product. retailer_url must be that product's URL. image_url must be that product's Primary image URL when one was found; if none was found, leave image_url empty rather than inventing or reusing another product's image. Two products must never share the same image_url. Brand must be the promoting brand name from the prompt, copied exactly.\n\n".implode("\n\n", $sections);

        $response = agent(
            instructions: 'You are an expert commerce editor. Return a fixed catalog JSON for a multi-product roundup landing page. Every product URL is a distinct product. Never collapse URLs. Never assign the same image_url to more than one product. Use scraped prices, ratings, and names when present. Write original editorial copy for summaries, pros, cons, and review-source angles. Only include review_sources with real publisher names and URLs you are confident about; otherwise return an empty list.',
            schema: fn (JsonSchema $schema) => $this->schema($schema),
        )->prompt($prompt, provider: TextProviderFailover::providers());

        $data = $response->toArray();

        $data['Brand'] = $brandName;

        if ($advertiserId) {
            $data['advertiser_id'] = $advertiserId;
        }

        if ($campaignId) {
            $data['campaign_id'] = $campaignId;
        }

        $data = $this->reconcileProducts($data, $productImages, $productUrls);

        Log::info('Catalog AI JSON', [
            'brand' => $brandName,
            'product_urls' => $productUrls,
            'json' => $data,
        ]);

        return CatalogPageData::fromArray($data);
    }

    /**
     * @return array<string, mixed>
     */
    private function schema(JsonSchema $schema): array
    {
        $product = $schema->object([
            'name' => $schema->string()->description('Product name as sold.')->required(),
            'brand' => $schema->string()->description('Manufacturer or product line brand.')->required(),
            'best_for' => $schema->string()->description('Roundup badge such as Best Overall.')->required(),
            'price' => $schema->string()->description('Current price with currency symbol when known.')->required(),
            'list_price' => $schema->string()->description('List price if discounted; empty string otherwise.')->required(),
            'discount' => $schema->string()->description('Discount label if any; empty string otherwise.')->required(),
            'rating' => $schema->string()->description('Star rating as a string, e.g. 4.7.')->required(),
            'review_count' => $schema->string()->description('Review count as displayed, e.g. 6,121.')->required(),
            'price_history' => $schema->string()->description('Price-history note if known; empty string otherwise.')->required(),
            'image_url' => $schema->string()->description('This product\'s own Primary image URL, or empty.')->required(),
            'retailer_url' => $schema->string()->description('The product URL for this item.')->required(),
            'chips' => $schema->array()->items($schema->string())->description('Short proof chips.')->required(),
            'convertibility' => $schema->string()->description('Empty string unless a convertibility claim exists.')->required(),
            'certification' => $schema->string()->description('Empty string unless a certification exists.')->required(),
            'summary' => $schema->string()->description('Long editorial summary.')->required(),
            'summary_short' => $schema->string()->description('One-sentence summary.')->required(),
            'why_it_stands_out' => $schema->string()->description('Why this pick belongs in the roundup.')->required(),
            'why_short' => $schema->string()->description('Short why-it-stands-out line.')->required(),
            'pros' => $schema->array()->items($schema->string())->description('Pros.')->required(),
            'cons' => $schema->array()->items($schema->string())->description('Cons.')->required(),
            'tradeoff_note' => $schema->string()->description('Tradeoff versus another pick.')->required(),
            'sources_note' => $schema->string()->description('Editorial and shopper-source note.')->required(),
        ]);

        return [
            'Brand' => $schema->string()->description('The promoting brand name from the prompt, copied exactly.')->required(),
            'advertiser_id' => $schema->string()->description('Advertiser id when provided; otherwise empty.')->required(),
            'campaign_id' => $schema->string()->description('Campaign id when provided; otherwise empty.')->required(),
            'subcategory' => $schema->string()->description('Product subcategory.')->required(),
            'category' => $schema->string()->description('Broader category.')->required(),
            'updated' => $schema->string()->description('Month and year, e.g. May 2026.')->required(),
            'hero_title' => $schema->string()->description('Hero headline for the roundup.')->required(),
            'review_sources' => $schema->array()->items(
                $schema->object([
                    'name' => $schema->string()->description('Publisher name.')->required(),
                    'url' => $schema->string()->description('Roundup or article URL.')->required(),
                    'angle' => $schema->string()->description('What that source contributed.')->required(),
                ])
            )->description('Editorial sources backing the roundup.')->required(),
            'products' => $schema->array()->items($product)->description('One object per product URL, same order.')->required(),
        ];
    }

    /**
     * @param  array<string, mixed>  $data
     * @param  list<string|null>  $scrapedImages
     * @param  list<string>  $productUrls
     * @return array<string, mixed>
     */
    private function reconcileProducts(array $data, array $scrapedImages, array $productUrls): array
    {
        $products = array_values($data['products'] ?? []);
        $seen = [];

        foreach ($productUrls as $index => $url) {
            $row = is_array($products[$index] ?? null) ? $products[$index] : [];
            $scraped = $scrapedImages[$index] ?? null;
            $aiImage = trim((string) ($row['image_url'] ?? ''));
            $image = filled($scraped) ? $scraped : ($aiImage !== '' ? $aiImage : '');

            if ($image !== '' && $this->scraper->isDecorativeImage($image)) {
                $image = '';
            }

            if ($image !== '' && isset($seen[$image])) {
                Log::warning('Catalog landing page: duplicate product image dropped', [
                    'url' => $url,
                    'image' => $image,
                ]);
                $image = '';
            }

            if ($image !== '') {
                $seen[$image] = true;
            }

            $row['retailer_url'] = $url;
            $row['image_url'] = $image;
            $products[$index] = CatalogProduct::fromArray($row, $url, $image)->toArray();
        }

        $data['products'] = $products;

        return $data;
    }
}
