<?php

namespace Modules\ProductCompat\Entities\Services;

use App\Product;
use Modules\ProductCompat\Entities\PcEnrichmentLog;
use Modules\ProductCompat\Entities\Enrichment\Providers\IcecatProvider;
use Modules\ProductCompat\Entities\Enrichment\Providers\GoogleCseProvider;
use Modules\ProductCompat\Entities\Enrichment\Providers\SearxngProvider;
use Modules\ProductCompat\Entities\Enrichment\Providers\SerpApiProvider;
use Modules\ProductCompat\Entities\Enrichment\Providers\PaidApiProvider;
use Modules\ProductCompat\Entities\Enrichment\Providers\OfficialSiteProvider;
use Modules\ProductCompat\Entities\Enrichment\Providers\TrustedSiteProvider;
use Modules\ProductCompat\Entities\Enrichment\Providers\AiEnrichProvider;
use Modules\ProductCompat\Entities\Enrichment\EnrichmentResult;

/**
 * Orchestrates enrichment: tries providers in the configured priority order,
 * logs every attempt to pc_enrichment_log, and applies the first provider's
 * candidates (as needs_review). Mirrors the LabCore driver-registry pattern.
 */
class EnrichmentManager
{
    protected ProductCompatService $service;

    /** @var array<string, \Modules\ProductCompat\Entities\Enrichment\EnrichmentProvider> */
    protected array $providers;

    public function __construct(ProductCompatService $service)
    {
        $this->service = $service;

        $parser = new CustomFieldParser();
        $this->providers = [
            'icecat'   => new IcecatProvider($parser),
            'searxng'  => new SearxngProvider($parser),
            'google'   => new GoogleCseProvider($parser),
            'serp'     => new SerpApiProvider($parser),
            'official' => new OfficialSiteProvider($parser),
            'trusted'  => new TrustedSiteProvider($parser),
            'api'      => new PaidApiProvider(),
            'ai'       => new AiEnrichProvider(),
        ];
    }

    /**
     * Providers in configured priority order, available ones only.
     *
     * @return array<int, \Modules\ProductCompat\Entities\Enrichment\EnrichmentProvider>
     */
    public function availableProviders(): array
    {
        $order = config('productcompat.enrichment.provider_priority', ['official', 'trusted', 'api']);
        $out = [];
        foreach ($order as $key) {
            if (isset($this->providers[$key]) && $this->providers[$key]->isAvailable()) {
                $out[] = $this->providers[$key];
            }
        }

        return $out;
    }

    /**
     * Enrich a single product. First provider that yields candidates wins.
     *
     * @return array{provider:?string, parts:int, devices:int, status:string}
     */
    public function enrichProduct(Product $product): array
    {
        $summary = ['provider' => null, 'parts' => 0, 'devices' => 0, 'barcodes' => 0, 'status' => 'no_provider'];

        foreach ($this->availableProviders() as $provider) {
            $log = new PcEnrichmentLog([
                'business_id' => $product->business_id,
                'product_id'  => $product->id,
                'provider'    => $provider->key(),
                'status'      => 'pending',
            ]);

            try {
                $result = $provider->fetch($product);
                $log->raw_payload = $this->trimRaw($result->raw);
                $log->candidates  = $result->candidates;

                if (! $result->ok) {
                    $log->status = 'error';
                    $log->message = $result->message;
                    $log->save();
                    $summary['status'] = 'error';
                    continue;
                }

                if (! $result->hasCandidates()) {
                    $log->status = 'empty';
                    $log->message = $result->message;
                    $log->save();
                    if ($summary['status'] === 'no_provider') {
                        $summary['status'] = 'empty';
                    }
                    continue;
                }

                $applied = $this->service->applyEnrichmentCandidates(
                    (int) $product->business_id, (int) $product->id, $result->candidates, $provider->key()
                );

                $log->status = 'success';
                $log->message = "added parts={$applied['parts']} devices={$applied['devices']} barcodes=" . ($applied['barcodes'] ?? 0);
                $log->save();

                return [
                    'provider' => $provider->key(),
                    'parts'    => $applied['parts'],
                    'devices'  => $applied['devices'],
                    'barcodes' => $applied['barcodes'] ?? 0,
                    'status'   => 'success',
                ];
            } catch (\Throwable $e) {
                $log->status = 'error';
                $log->message = $e->getMessage();
                $log->save();
                $summary['status'] = 'error';
            }
        }

        return $summary;
    }

    /**
     * Collect from ALL available providers (aggressive mode). Unlike
     * enrichProduct(), this NEVER applies anything and NEVER stops at the first
     * hit — it runs every provider, logs each attempt to pc_enrichment_log with
     * the same trimming/log style, and returns the raw results keyed by provider
     * for the AggressiveEnrichmentService to merge/apply.
     *
     * @return array<string, EnrichmentResult>
     */
    public function collectAll(Product $product): array
    {
        $out = [];

        foreach ($this->availableProviders() as $provider) {
            $log = new PcEnrichmentLog([
                'business_id' => $product->business_id,
                'product_id'  => $product->id,
                'provider'    => $provider->key(),
                'status'      => 'pending',
            ]);

            try {
                $result = $provider->fetch($product);
                $log->raw_payload = $this->trimRaw($result->raw);
                $log->candidates  = $result->candidates;

                if (! $result->ok) {
                    $log->status = 'error';
                    $log->message = $result->message;
                } elseif (! $result->hasCandidates() && empty($result->rich)) {
                    $log->status = 'empty';
                    $log->message = $result->message;
                } else {
                    $log->status = 'success';
                    $log->message = 'parts=' . count($result->candidates['part_numbers'])
                        . ' devices=' . count($result->candidates['device_models'])
                        . ' barcodes=' . count($result->candidates['barcodes'])
                        . ' rich=' . (empty($result->rich) ? 0 : 1);
                }
                $log->save();

                $out[$provider->key()] = $result;
            } catch (\Throwable $e) {
                $log->status = 'error';
                $log->message = $e->getMessage();
                $log->save();
                // Record a failed result so downstream code can see the attempt.
                $out[$provider->key()] = EnrichmentResult::fail($e->getMessage());
            }
        }

        return $out;
    }

    /**
     * Keep the stored raw payload from bloating the log table.
     */
    protected function trimRaw(mixed $raw): mixed
    {
        if (is_string($raw)) {
            return ['body_excerpt' => mb_substr($raw, 0, 2000)];
        }

        return $raw;
    }
}
