<?php

namespace Modules\ProductCompat\Console;

use App\Product;
use Illuminate\Console\Command;
use Modules\ProductCompat\Entities\PcDataCandidate;
use Modules\ProductCompat\Entities\PcSearchQueue;
use Modules\ProductCompat\Entities\Services\ProductCompatService;

/**
 * Import an OFFLINE-enriched JSON batch (produced from productcompat:export-products
 * and filled in by an AI assistant) back into the review-first pipeline. Structured
 * identifiers land as needs-review pc_* rows; description / specs / corrections land
 * as pending pc_data_candidates — exactly like the online queue, so everything is
 * confirmed in the Review Queue before touching a product.
 *
 *   php artisan productcompat:import-enrichment storage/app/pc_batch_enriched.json
 *
 * Expected JSON: array of objects, each:
 * { "id":123, "part_numbers":[{"value":"","type":"oem"}], "device_models":[{"brand":"","series":"","model":""}],
 *   "barcodes":[{"value":"","type":"gtin"}], "description":"", "specs":[{"label":"","value":""}],
 *   "corrections":[{"kind":"part_number","current_value":"","problem":"","suggested_value":""}] }
 */
class ImportEnrichment extends Command
{
    protected $signature = 'productcompat:import-enrichment
        {file : Path to an enriched JSON file OR a directory of *.enriched.json files}
        {--confidence=70 : Confidence to stamp on offline candidates (0-100)}';

    protected $description = 'Import an offline-enriched JSON batch as review-first candidates.';

    public function handle(ProductCompatService $service): int
    {
        $path = $this->argument('file');

        // A directory imports every *.enriched.json inside it (batch workflow).
        if (is_dir($path)) {
            $files = glob(rtrim($path, '/\\') . '/*.enriched.json') ?: [];
            if (empty($files)) {
                $this->error('No *.enriched.json files found in: ' . $path);
                return self::FAILURE;
            }
            $conf = max(0, min(100, (int) $this->option('confidence')));
            $grand = 0;
            foreach ($files as $f) {
                $this->line('→ ' . basename($f));
                $grand += $this->importFile($f, $service, $conf);
            }
            $this->info("Done. Imported {$grand} product(s) across " . count($files) . ' file(s).');
            return self::SUCCESS;
        }

        if (! is_file($path)) {
            $this->error('File not found: ' . $path);
            return self::FAILURE;
        }

        $conf = max(0, min(100, (int) $this->option('confidence')));
        $this->importFile($path, $service, $conf);

        return self::SUCCESS;
    }

    /** Import one enriched JSON file; returns the number of products applied. */
    protected function importFile(string $file, ProductCompatService $service, int $conf): int
    {
        $data = json_decode((string) file_get_contents($file), true);
        if (! is_array($data)) {
            $this->error('  Not a valid JSON array — skipped.');
            return 0;
        }

        $runId  = 'offline_' . now()->format('YmdHis');
        $totals = ['products' => 0, 'parts' => 0, 'devices' => 0, 'barcodes' => 0, 'candidates' => 0, 'skipped' => 0];

        foreach ($data as $row) {
            $pid = (int) ($row['id'] ?? 0);
            $product = $pid ? Product::find($pid) : null;
            if (! $product) {
                $totals['skipped']++;
                continue;
            }
            $biz = (int) $product->business_id;

            // 1. Structured identifiers → needs-review pc_* rows (add-only).
            $candidates = [
                'part_numbers'  => array_map(fn ($p) => [
                    'value'      => (string) ($p['value'] ?? ''),
                    'type'       => (string) ($p['type'] ?? 'other'),
                    'confidence' => $conf,
                ], array_filter((array) ($row['part_numbers'] ?? []), fn ($p) => ! empty($p['value']))),
                'device_models' => array_map(fn ($d) => [
                    'brand'      => (string) ($d['brand'] ?? ''),
                    'series'     => (string) ($d['series'] ?? ''),
                    'model'      => (string) ($d['model'] ?? ''),
                    'confidence' => $conf,
                ], array_filter((array) ($row['device_models'] ?? []), fn ($d) => ! empty($d['model']))),
                'barcodes'      => array_map(fn ($b) => [
                    'value'      => preg_replace('/\D/', '', (string) ($b['value'] ?? '')),
                    'type'       => (string) ($b['type'] ?? 'gtin'),
                    'confidence' => $conf,
                ], array_filter((array) ($row['barcodes'] ?? []), fn ($b) => ! empty($b['value']))),
            ];

            $applied = $service->applyEnrichmentCandidates($biz, $pid, $candidates, 'offline');
            $totals['parts']    += (int) ($applied['parts'] ?? 0);
            $totals['devices']  += (int) ($applied['devices'] ?? 0);
            $totals['barcodes'] += (int) ($applied['barcodes'] ?? 0);

            // 2. Rich fields → pending pc_data_candidates (create-only, never
            //    resurrect a rejected/applied one).
            $rich = [];
            if (! empty($row['description'])) {
                $rich[] = ['field' => 'description', 'value' => mb_substr(strip_tags((string) $row['description']), 0, 1200), 'extra' => null];
            }
            if (! empty($row['specs']) && is_array($row['specs'])) {
                $specs = [];
                foreach ($row['specs'] as $s) {
                    if (! empty($s['label']) && ! empty($s['value'])) {
                        $specs[] = ['label' => mb_substr((string) $s['label'], 0, 120), 'value' => mb_substr((string) $s['value'], 0, 120)];
                    }
                }
                if ($specs) {
                    $rich[] = ['field' => 'specs', 'value' => json_encode($specs, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 'extra' => null];
                }
            }
            foreach ((array) ($row['corrections'] ?? []) as $c) {
                if (empty($c['kind']) || empty($c['current_value']) || empty($c['problem'])) {
                    continue;
                }
                $kind    = (string) $c['kind'];
                $current = (string) $c['current_value'];
                // Resolve the pc_* row this correction (incl. remove_*) targets so
                // approving it in the Review Queue actually deletes the right row.
                $target = $service->resolveCorrectionTarget($pid, $kind, $current);
                if ($target === 'verified') {
                    continue; // human-verified value — never flag it
                }
                $rich[] = [
                    'field' => 'correction',
                    'value' => $current,
                    'extra' => [
                        'kind'         => $kind,
                        'current'      => $current,
                        'suggested'    => (string) ($c['suggested_value'] ?? ''),
                        'problem'      => (string) $c['problem'],
                        'target_table' => is_array($target) ? ($target['table'] ?? null) : null,
                        'target_id'    => is_array($target) ? ($target['id'] ?? null) : null,
                    ],
                ];
            }

            // Compatible-product refs → pending 'compatible' candidates (matched
            // to real product ids later by productcompat:match-compatibility).
            foreach ((array) ($row['compatible_products'] ?? []) as $cp) {
                $matchBy = (string) ($cp['match_by'] ?? '');
                $value   = (string) ($cp['value'] ?? '');
                if (! in_array($matchBy, ['sku', 'part_number', 'name'], true) || $value === '') {
                    continue;
                }
                $rich[] = [
                    'field' => 'compatible',
                    'value' => $matchBy . ':' . $value,
                    'extra' => [
                        'match_by' => $matchBy,
                        'value'    => $value,
                        'relation' => (string) ($cp['relation'] ?? 'compatible'),
                        'note'     => (string) ($cp['note'] ?? ''),
                        'linked'   => false,
                    ],
                ];
            }

            foreach ($rich as $r) {
                $hash = PcDataCandidate::makeHash($r['field'], (string) $r['value']);
                $exists = PcDataCandidate::where('product_id', $pid)->where('field', $r['field'])->where('value_hash', $hash)->exists();
                if ($exists) {
                    continue;
                }
                PcDataCandidate::create([
                    'business_id' => $biz,
                    'product_id'  => $pid,
                    'field'       => $r['field'],
                    'value'       => $r['value'],
                    'value_hash'  => $hash,
                    'extra'       => $r['extra'],
                    'source'      => 'offline',
                    'confidence'  => $conf,
                    'status'      => 'pending',
                    'run_id'      => $runId,
                ]);
                $totals['candidates']++;
            }

            // Auto-collapse exact-duplicate part numbers / barcodes on this product.
            $service->dedupeIdentifiers($biz, $pid);

            // Mark any queue row for this product done so it isn't re-processed.
            PcSearchQueue::where('business_id', $biz)->where('product_id', $pid)
                ->whereIn('status', ['queued', 'processing'])
                ->update(['status' => 'done', 'processed_at' => now()]);

            $totals['products']++;
        }

        $this->info(sprintf(
            '  %d product(s): +%d parts, +%d devices, +%d barcodes, +%d rich candidate(s). Skipped %d.',
            $totals['products'], $totals['parts'], $totals['devices'], $totals['barcodes'], $totals['candidates'], $totals['skipped']
        ));

        return $totals['products'];
    }
}
