<?php

namespace Modules\ProductCompat\Entities\Services;

use App\Product;
use Modules\ProductCompat\Entities\PcCompatible;
use Modules\ProductCompat\Entities\PcPartNumber;

/**
 * Resolves "compatible product" references to real product ids in the DB and
 * writes review-first pc_compatibles links, both directions. Two sources:
 *   - AI-suggested refs ({match_by, value, relation}) → linkAiRefs()
 *   - shared data: products that share a part number or device model → autoLinkShared()
 * Runs as the FINAL phase (productcompat:match-compatibility), once every
 * product's own part numbers are cleaned, so matches are trustworthy.
 */
class CompatibilityLinker
{
    /**
     * Link a product to other DB products named in AI refs. Returns links made.
     *
     * @param array $refs each ['match_by'=>sku|part_number|name, 'value'=>string, 'relation'=>string]
     */
    public function linkAiRefs(int $businessId, int $productId, array $refs, string $source = 'ai'): int
    {
        $made = 0;
        foreach ($refs as $ref) {
            $matchBy = (string) ($ref['match_by'] ?? '');
            $value   = trim((string) ($ref['value'] ?? ''));
            $relation = $this->relation((string) ($ref['relation'] ?? 'compatible'));
            if ($value === '') {
                continue;
            }

            foreach ($this->resolveTargets($businessId, $matchBy, $value, $productId) as $targetId) {
                $made += $this->linkPair($businessId, $productId, $targetId, $relation, $source);
            }
        }

        return $made;
    }

    /**
     * Link products that are genuinely interchangeable with $productId: they
     * share an exact (normalized) part number → relation 'replacement'. We do
     * NOT auto-link on shared device model — "both fit laptop X" does not make a
     * battery and a charger interchangeable, and it explodes combinatorially.
     * Cross-product compatibility beyond shared part numbers comes from the AI's
     * explicit compatible_products refs (linkAiRefs). Capped per product to keep
     * common part numbers (listed on many SKUs) from flooding review.
     * Returns links made.
     */
    public function autoLinkShared(int $businessId, int $productId): int
    {
        $cap = (int) config('productcompat.compat_shared_cap', 25);

        $norms = PcPartNumber::where('product_id', $productId)->pluck('normalized')->filter()->unique()->all();
        if (empty($norms)) {
            return 0;
        }

        $others = PcPartNumber::where('business_id', $businessId)
            ->whereIn('normalized', $norms)
            ->where('product_id', '!=', $productId)
            ->distinct()->limit($cap)->pluck('product_id');

        $made = 0;
        foreach ($others as $otherId) {
            $made += $this->linkPair($businessId, $productId, (int) $otherId, 'replacement', 'shared');
        }

        return $made;
    }

    /** Resolve a ref to matching product ids in the DB (same business). */
    protected function resolveTargets(int $businessId, string $matchBy, string $value, int $excludeId): array
    {
        $q = Product::where('business_id', $businessId)->where('id', '!=', $excludeId);

        if ($matchBy === 'sku') {
            return $q->where('sku', $value)->pluck('id')->map(fn ($i) => (int) $i)->all();
        }
        if ($matchBy === 'name') {
            return $q->whereRaw('LOWER(name) = ?', [mb_strtolower($value)])->pluck('id')->map(fn ($i) => (int) $i)->all();
        }
        if ($matchBy === 'part_number') {
            $norm = PcPartNumber::normalize($value);
            if ($norm === '') {
                return [];
            }
            return PcPartNumber::where('business_id', $businessId)
                ->where('normalized', $norm)
                ->where('product_id', '!=', $excludeId)
                ->distinct()->pluck('product_id')->map(fn ($i) => (int) $i)->all();
        }

        return [];
    }

    /** Create the bidirectional pc_compatibles rows (review-first). Returns rows made. */
    protected function linkPair(int $businessId, int $a, int $b, string $relation, string $source): int
    {
        if ($a === $b || $b < 1) {
            return 0;
        }
        $made = 0;
        foreach ([[$a, $b], [$b, $a]] as [$from, $to]) {
            $row = PcCompatible::firstOrNew([
                'product_id'            => $from,
                'compatible_product_id' => $to,
                'relation_type'         => $relation,
            ]);
            if (! $row->exists) {
                $row->fill([
                    'business_id'  => $businessId,
                    'source'       => $source,
                    'confidence'   => 70,
                    'needs_review' => true,
                ])->save();
                $made++;
            }
        }

        return $made;
    }

    protected function relation(string $r): string
    {
        $r = strtolower(trim($r));

        return in_array($r, ['compatible', 'replacement', 'accessory'], true) ? $r : 'compatible';
    }
}
