<?php

namespace App\Jobs;

use App\Contracts\CatalogProductRanker;
use App\Contracts\KeywordResearchService;
use App\Enums\LandingPageLayoutMode;
use App\Models\LandingPageGeneration;
use App\Services\AI\CatalogWebsiteResearch;
use App\Services\Catalog\CatalogProductImportValidator;
use App\Services\CatalogTemplateLocator;
use App\Services\Keywords\CatalogKeywordSeedResolver;
use App\Services\Keywords\KeywordCandidateEnricher;
use App\Services\Keywords\KeywordIdeaCurator;
use App\Services\Ranking\CatalogProductRankingExplainer;
use App\Services\Scraping\CatalogListingProductValidator;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Throwable;

class DiscoverCatalogProductsJob implements ShouldQueue
{
    use Queueable;

    public int $timeout = 2000;

    public int $tries = 1;

    public function __construct(public int $generationId) {}

    public function handle(
        CatalogWebsiteResearch $research,
        CatalogTemplateLocator $locator,
        CatalogProductRanker $ranker,
        KeywordResearchService $keywordResearch,
        CatalogKeywordSeedResolver $keywordSeedResolver,
        KeywordCandidateEnricher $keywordEnricher,
        KeywordIdeaCurator $keywordIdeaCurator,
        CatalogProductRankingExplainer $rankingExplainer,
        CatalogListingProductValidator $listingProductValidator,
    ): void {
        $generation = LandingPageGeneration::query()->findOrFail($this->generationId);

        if ($generation->status !== 'discovering' || $generation->layout_mode !== LandingPageLayoutMode::Catalog) {
            return;
        }

        $researchState = $generation->catalogResearch();
        $category = trim((string) ($researchState['category'] ?? ''));
        $subcategory = $this->subcategoryLabel($researchState);

        try {
            $source = (string) ($researchState['source'] ?? 'website');
            $candidates = $source === 'json'
                ? $this->uploadedCandidates($researchState)
                : $research->topProducts(
                    (string) $generation->brand_url,
                    $category,
                    $subcategory,
                    $this->listingUrls($researchState, (string) $generation->brand_url),
                );

            if ($source !== 'json') {
                Log::info('Catalog product listing URLs', [
                    'generation_id' => $generation->id,
                    'category' => $category,
                    'subcategory' => $subcategory,
                    'source' => $source,
                    'listing_urls' => $this->listingUrls($researchState, (string) $generation->brand_url),
                ]);
            } else {
                Log::info('Catalog product import using uploaded JSON', [
                    'generation_id' => $generation->id,
                    'candidate_count' => count($candidates),
                ]);
            }

            $candidates = $listingProductValidator->validateAndEnrich(
                $candidates,
                trustProvidedImages: $source === 'json',
            );

            Log::info('Catalog product candidates validated', [
                'generation_id' => $generation->id,
                'count' => count($candidates),
                'product_urls' => array_column($candidates, 'url'),
            ]);

            $keywordSeeds = [];
            $userKeywordSeeds = [];
            $aiKeywordSeeds = [];
            $keywords = [];
            $keywordIdeasFetched = 0;

            if (config('google-ads.enabled')) {
                $seedResolution = $keywordSeedResolver->resolve(
                    (string) $generation->brand_name,
                    $category,
                    $subcategory,
                    $candidates,
                );

                $keywordSeeds = $seedResolution['seeds'];
                $userKeywordSeeds = $seedResolution['user_seeds'];
                $aiKeywordSeeds = $seedResolution['ai_seeds'];
                $candidates = $this->attachProductKeywordSeeds(
                    $candidates,
                    $seedResolution['product_seeds'] ?? [],
                );

                if ($keywordSeeds !== []) {
                    $rawKeywords = $keywordResearch->ideasForSeeds($keywordSeeds);
                    $keywordIdeasFetched = count($rawKeywords);
                    $keywords = $keywordIdeaCurator->curate(
                        $rawKeywords,
                        $keywordSeeds,
                        $candidates,
                        $category,
                        $subcategory,
                    );
                    $candidates = $keywordEnricher->enrich($candidates, $keywords, $category, $subcategory);
                }

                Log::info('Catalog keyword research finished', [
                    'generation_id' => $generation->id,
                    'seed_count' => count($keywordSeeds),
                    'user_seed_count' => count($userKeywordSeeds),
                    'ai_seed_count' => count($aiKeywordSeeds),
                    'keyword_ideas_fetched' => $keywordIdeasFetched,
                    'keyword_count' => count($keywords),
                ]);
            }

            $products = $this->selectProductsForCatalog(
                $ranker,
                $candidates,
                $category,
                $subcategory,
                $this->catalogNotes($category, $subcategory, $researchState),
            );

            $products = $rankingExplainer->explain($products);
            $minimumProducts = $this->minimumSelectedProducts($source);

            Log::info('Catalog product URLs ranked', [
                'generation_id' => $generation->id,
                'candidate_count' => count($candidates),
                'selected_count' => count($products),
                'product_urls' => array_column($products, 'url'),
            ]);

            $generation->mergeCatalogResearch([
                'products' => $products,
                'candidate_count' => count($candidates),
                'keyword_seeds' => $keywordSeeds,
                'user_keyword_seeds' => $userKeywordSeeds,
                'ai_keyword_seeds' => $aiKeywordSeeds,
                'keywords' => $keywords,
                'keyword_ideas_fetched' => $keywordIdeasFetched,
                'keyword_ideas_curated_count' => count($keywords),
                'ranking' => [
                    'enabled' => (bool) config('scraping.ranking.enabled', true),
                    'ai_enabled' => (bool) config('scraping.ranking.ai_enabled', true),
                    'keywords_enabled' => (bool) config('google-ads.enabled', false),
                    'limit' => (int) config('scraping.ranking.limit', 10),
                    'selected_count' => count($products),
                    'source' => $products !== [] ? ($products[0]['ranking_source'] ?? null) : null,
                ],
                'listing_scrape_error' => $research->lastListingScrapeError(),
                'empty_reason' => count($products) < $minimumProducts
                    ? $this->emptyReasonForProducts($research->lastListingScrapeError())
                    : null,
            ]);

            if (count($products) < $minimumProducts) {
                $generation->update([
                    'status' => $this->emptyProductsStatus($researchState),
                    'failure_reason' => null,
                ]);

                return;
            }

            if ($locator->slots() === [] || $locator->sharedTemplates() === []) {
                $generation->update([
                    'status' => 'failed',
                    'failure_reason' => 'Add at least one template folder (for example template-1 with index.phtml) under resources/landing-page-templates/{variation}.',
                ]);

                return;
            }

            $generation->update([
                'product_urls' => array_column($products, 'url'),
                'notes' => $this->catalogNotes($category, $subcategory, $researchState),
                'status' => 'awaiting_template',
                'failure_reason' => null,
            ]);
        } catch (Throwable $e) {
            $generation->update([
                'status' => 'failed',
                'failure_reason' => $e->getMessage(),
            ]);

            throw $e;
        }
    }

    /**
     * @param  array<string, mixed>  $research
     * @return list<array<string, mixed>>
     */
    private function uploadedCandidates(array $research): array
    {
        $rows = $research['uploaded_products'] ?? [];

        if (! is_array($rows)) {
            return [];
        }

        return array_values(array_filter($rows, fn (mixed $row): bool => is_array($row)));
    }

    /**
     * @param  array<string, mixed>  $research
     * @return list<string>
     */
    private function listingUrls(array $research, string $brandUrl): array
    {
        $urls = [];

        foreach ($research['selected_subcategories'] ?? [] as $row) {
            if (! is_array($row)) {
                continue;
            }

            $url = trim((string) ($row['url'] ?? ''));

            if ($url !== '' && ! in_array($url, $urls, true)) {
                $urls[] = $url;
            }
        }

        if ($urls === [] && filled($research['listing_url'] ?? null)) {
            $urls[] = (string) $research['listing_url'];
        }

        if ($urls === [] && filled($research['subcategory_url'] ?? null)) {
            $urls[] = (string) $research['subcategory_url'];
        }

        if ($urls === [] && filled($research['category_url'] ?? null)) {
            $urls[] = (string) $research['category_url'];
        }

        if ($urls === []) {
            $urls[] = $brandUrl;
        }

        return $urls;
    }

    /**
     * @param  array<string, mixed>  $research
     */
    private function subcategoryLabel(array $research): string
    {
        $names = [];

        foreach ($research['selected_subcategories'] ?? [] as $row) {
            if (! is_array($row)) {
                continue;
            }

            $name = trim((string) ($row['name'] ?? ''));

            if ($name !== '' && ! in_array($name, $names, true)) {
                $names[] = $name;
            }
        }

        if ($names !== []) {
            return implode(', ', $names);
        }

        return trim((string) ($research['subcategory'] ?? ''));
    }

    /**
     * @param  array<string, mixed>  $research
     */
    private function emptyProductsStatus(array $research): string
    {
        if (in_array($research['source'] ?? '', ['listing', 'json'], true)) {
            return 'awaiting_products';
        }

        if ($this->namedChoices($research['subcategories'] ?? null) !== []) {
            return 'awaiting_subcategory';
        }

        return 'awaiting_category';
    }

    private function emptyReasonForProducts(?string $listingScrapeError): string
    {
        $error = Str::lower(trim((string) $listingScrapeError));

        if ($error === '') {
            return 'no_products';
        }

        if (str_contains($error, 'blocked automated access')
            || str_contains($error, 'bot protection')
            || str_contains($error, 'access denied')
            || str_contains($error, 'captcha')
            || str_contains($error, 'human verification')) {
            return 'blocked';
        }

        return 'no_products';
    }

    /**
     * @param  array<string, mixed>  $research
     */
    private function catalogNotes(string $category, string $subcategory, array $research): string
    {
        $parts = [];

        if (($research['source'] ?? '') === 'listing') {
            $listingUrl = trim((string) ($research['listing_url'] ?? ''));

            if ($listingUrl !== '') {
                $parts[] = "Product listing: {$listingUrl}.";
            }

            $parts[] = 'Use the top-rated or most popular products.';

            return implode(' ', $parts);
        }

        if (($research['source'] ?? '') === 'json') {
            $parts[] = 'Products imported from uploaded JSON.';

            return implode(' ', $parts);
        }

        if ($category !== '') {
            $parts[] = "Category: {$category}.";
        }

        $selectedCount = $this->namedChoices($research['selected_subcategories'] ?? null);

        if ($subcategory !== '') {
            $parts[] = count($selectedCount) > 1
                ? "Subcategories: {$subcategory}."
                : "Subcategory: {$subcategory}.";
        }

        $parts[] = 'Use the top-rated or most popular products.';

        return implode(' ', $parts);
    }

    /**
     * @return list<array{name: string, url: string}>
     */
    private function namedChoices(mixed $items): array
    {
        $choices = [];

        foreach (is_array($items) ? $items : [] as $row) {
            if (! is_array($row)) {
                continue;
            }

            $name = trim((string) ($row['name'] ?? ''));

            if ($name === '') {
                continue;
            }

            $choices[] = [
                'name' => $name,
                'url' => (string) ($row['url'] ?? ''),
            ];
        }

        return $choices;
    }

    /**
     * @param  list<array<string, mixed>>  $candidates
     * @return list<array<string, mixed>>
     */
    private function selectProductsForCatalog(
        CatalogProductRanker $ranker,
        array $candidates,
        string $category,
        string $subcategory,
        string $notes,
    ): array {
        if ($candidates === []) {
            return [];
        }

        if (config('scraping.ranking.enabled', true)) {
            return $ranker->rank($candidates, $category, $subcategory, $notes);
        }

        $limit = max(1, (int) config('scraping.ranking.limit', 10));

        $selected = array_slice($candidates, 0, $limit);

        return array_values(array_map(
            fn (array $candidate, int $index): array => array_merge($candidate, ['rank' => $index + 1]),
            $selected,
            array_keys($selected),
        ));
    }

    /**
     * @param  list<array<string, mixed>>  $candidates
     * @param  list<array{user: list<string>, ai: list<string>, heuristic: list<string>}>  $productSeeds
     * @return list<array<string, mixed>>
     */
    private function attachProductKeywordSeeds(array $candidates, array $productSeeds): array
    {
        foreach ($candidates as $index => &$candidate) {
            $generated = $productSeeds[$index] ?? null;

            if (! is_array($generated)) {
                continue;
            }

            $candidate['keyword_seeds_generated'] = [
                'user' => array_values($generated['user'] ?? []),
                'ai' => array_values($generated['ai'] ?? []),
                'heuristic' => array_values($generated['heuristic'] ?? []),
            ];
        }

        unset($candidate);

        return $candidates;
    }

    private function minimumSelectedProducts(string $source): int
    {
        return $source === 'json'
            ? CatalogProductImportValidator::MIN_PRODUCTS
            : 5;
    }
}
