<?php

namespace App\Services;

use App\Enums\PreProcessErrorCode;
use App\Enums\PricelistWorkflowStep;
use App\Models\PricelistFile;
use App\Enums\PricelistFileStatusEnum;
use App\Models\PricelistFileLine;
use App\Enums\PricelistFileLineStatusEnum;
use App\Enums\PricelistUnitEnum;
use App\Enums\PricelistSellerUnitEnum;
use App\Enums\PricelistVatEnum;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;

class PricelistFileProcessService
{
    protected const PRODUCT_FIELD_MAP = [
        'name' => [
            'header' => PriceListService::EXPECTED_HEADERS[4], // Termék megnevezése
            'type' => 'string'
        ],
        'unitValue' => [
            'header' => PriceListService::EXPECTED_HEADERS[6], // Súly/Űrtartalom (nettó)
            'type' => 'float'
        ],
        'productUnit' => [
            'header' => PriceListService::EXPECTED_HEADERS[7], // Mértékegység
            'type' => 'string'
        ],
        'sellerUnit' => [
            'header' => PriceListService::EXPECTED_HEADERS[9], // Legkisebb eladási egység
            'type' => 'string'
        ],
        'unitMultiplier' => [
            'header' => PriceListService::EXPECTED_HEADERS[10], // Egységszorzó
            'type' => 'float'
        ],
        'amountUnit' => [
            'header' => PriceListService::EXPECTED_HEADERS[11], // Mennyiségi egység
            'type' => 'string'
        ],
        'vat' => [
            'header' => PriceListService::EXPECTED_HEADERS[12], // ÁFA %
            'type' => 'float'
        ],
        'hooreycaId' => [
            'header' => PriceListService::EXPECTED_HEADERS[16], // Fix Hooreyca ID
            'type' => 'string'
        ],
        'HooreycaUnit' => [
            'header' => PriceListService::EXPECTED_HEADERS[17], // Hooreyca mértékegység
            'type' => 'string'
        ],
        'HooreycaMultiplier' => [
            'header' => PriceListService::EXPECTED_HEADERS[18], // Hooreyca mennyiségi szorzó
            'type' => 'float'
        ],
    ];

    public function __construct(
        protected PriceListService $priceListService
    ) {}

    /**
     * Alaphelyzetbe állítja a workflow-t és az állapotot az újraindításhoz
     */
    public function resetWorkflow(PricelistFile $pricelistFile): void
    {
        $pricelistFile->update([
            'workflow_steps' => PricelistWorkflowStep::defaultSteps(),
            'status' => PricelistFileStatusEnum::todo,
            'processing_current_step' => null,
            'processing_current_step_percentage' => null,
            'file_meta' => null,
        ]);
    }

    /**
     * Elindítja az árlista feldolgozás kezdeti láncát (Pre-processing -> Validation)
     */
    public function dispatchInitialChain(PricelistFile $pricelistFile): void
    {
        $this->resetWorkflow($pricelistFile);

        Bus::chain([
            PricelistWorkflowStep::Preprocessing->getJob($pricelistFile),
            PricelistWorkflowStep::Validation->getJob($pricelistFile),
        ])->dispatch();
    }

    /**
     * Elindítja a végrehajtási jobot (Approval után)
     */
    public function dispatchExecutionJob(PricelistFile $pricelistFile): void
    {
        $job = PricelistWorkflowStep::Execution->getJob($pricelistFile);
        if ($job) {
            dispatch($job);
        }
    }

    public function updateStepStatus(
        PricelistFile $pricelistFile,
        PricelistWorkflowStep $stepEnum,
        string $status,
        ?string $message = null,
        ?int $percentage = 0
    ): void {
        $effectiveMessage = $message ?? $stepEnum->label();

        // Frissítjük a modellt az adatbázisból, hogy a legfrissebb workflow_steps-szel dolgozzunk
        if ($pricelistFile->exists) {
            $pricelistFile->refresh();
        }

        $steps = $pricelistFile->workflow_steps ?? [];
        $found = false;

        foreach ($steps as &$step) {
            if ($step['name'] === $stepEnum->value) {
                $step['status'] = $status;
                $step['message'] = $effectiveMessage;
                $found = true;
                break;
            }
        }
        unset($step);

        // Ha valamiért nem találtuk meg a lépést (pl. régi rekord), adjuk hozzá a listához
        if (!$found) {
            $steps[] = [
                'name' => $stepEnum->value,
                'label' => $stepEnum->label(),
                'status' => $status,
                'message' => $effectiveMessage,
            ];
        }

        $updateData = [
            'workflow_steps' => $steps,
            'processing_current_step' => mb_strcut(
                ($message && $message !== $stepEnum->label())
                    ? $stepEnum->label() . ': ' . $message
                    : $effectiveMessage,
                0,
                255
            ),
            'processing_current_step_percentage' => $percentage,
        ];

        if ($status === 'inprogress') {
            if ($stepEnum === PricelistWorkflowStep::Approval) {
                $updateData['status'] = PricelistFileStatusEnum::waiting_for_approval;
                $updateData['processing_current_step'] = null;
                $updateData['processing_current_step_percentage'] = null;
            } else {
                $updateData['status'] = PricelistFileStatusEnum::inprogress;
            }
        } elseif ($status === 'failed') {
            $updateData['status'] = PricelistFileStatusEnum::fail;
        }

        $pricelistFile->update($updateData);
    }

    private const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB
    private const MIN_FILE_SIZE_BYTES = 1024; // 1 KB
    private const ALLOWED_EXTENSIONS = ['xlsx', 'xls'];

    /**
     * Előfeldolgozás (Pre-processing) - Strukturális és technikai ellenőrzés
     *
     * @param PricelistFile $pricelistFile
     * @return bool
     */
    public function preProcess(PricelistFile $pricelistFile): bool
    {
        $startTime = microtime(true);
        $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Preprocessing, 'inprogress', 'Fájl ellenőrzése...', 10);

        $meta = [
            'completed_at' => null,
            'duration_seconds' => null,
            'file_info' => [],
            'statistics' => [],
            'errors' => [],
            'warnings' => [],
            'is_processable' => true,
        ];

        try {
            // 1. Fájl fizikai ellenőrzése (10-20%)
            $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Preprocessing, 'inprogress', 'Fájl fizikai ellenőrzése...', 15);
            $fileCheckResult = $this->checkFilePhysical($pricelistFile);
            $meta['file_info'] = $fileCheckResult['info'];
            $meta['errors'] = array_merge($meta['errors'], $fileCheckResult['errors']);
            $meta['warnings'] = array_merge($meta['warnings'], $fileCheckResult['warnings']);

            if ($this->hasBlockerError($meta['errors'])) {
                $meta['is_processable'] = false;
                $this->saveMetaAndFail($pricelistFile, $meta, $startTime);
                return false;
            }

            // 2. Excel megnyitás és munkalap ellenőrzés (20-40%)
            $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Preprocessing, 'inprogress', 'Munkalap ellenőrzése...', 30);
            $fullPath = Storage::disk('public')->path($pricelistFile->filename);

            try {
                $spreadsheet = IOFactory::load($fullPath);
                $worksheet = $spreadsheet->getActiveSheet();
                $meta['file_info']['sheet_name'] = $worksheet->getTitle();
            } catch (\Exception $e) {
                $meta['errors'][] = $this->buildError(PreProcessErrorCode::FILE_CORRUPTED, $e->getMessage());
                $meta['is_processable'] = false;
                $this->saveMetaAndFail($pricelistFile, $meta, $startTime);
                return false;
            }

            // 3. Oszlopstruktúra validálás (40-70%)
            $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Preprocessing, 'inprogress', 'Oszlopstruktúra ellenőrzése...', 50);
            $structureResult = $this->validateColumnStructure($worksheet);
            $meta['errors'] = array_merge($meta['errors'], $structureResult['errors']);
            $meta['warnings'] = array_merge($meta['warnings'], $structureResult['warnings']);

            if ($this->hasBlockerError($meta['errors'])) {
                $meta['is_processable'] = false;
                $this->saveMetaAndFail($pricelistFile, $meta, $startTime);
                return false;
            }

            // 4. Statisztika gyűjtés (70-90%)
            $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Preprocessing, 'inprogress', 'Statisztikai adatok gyűjtése...', 75);
            $meta['statistics'] = $this->collectStatistics($worksheet);

            if ($meta['statistics']['data_rows'] === 0) {
                $meta['errors'][] = $this->buildError(PreProcessErrorCode::NO_DATA_ROWS);
                $meta['is_processable'] = false;
                $this->saveMetaAndFail($pricelistFile, $meta, $startTime);
                return false;
            }

            // 5. Sikeres befejezés (100%)
            $meta['completed_at'] = now()->toDateTimeString();
            $meta['duration_seconds'] = round(microtime(true) - $startTime, 2);

            $pricelistFile->update(['file_meta' => ['preprocess' => $meta]]);
            $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Preprocessing, 'completed', 'Előfeldolgozás sikeres.', 100);

            return true;

        } catch (\Exception $e) {
            Log::error('Pricelist pre-process error: ' . $e->getMessage());
            $meta['errors'][] = [
                'code' => 'UNEXPECTED_ERROR',
                'message' => $e->getMessage(),
                'severity' => 'blocker',
            ];
            $meta['is_processable'] = false;
            $this->saveMetaAndFail($pricelistFile, $meta, $startTime);
            throw $e;
        }
    }

    /**
     * Fájl fizikai ellenőrzése (létezés, kiterjesztés, méret)
     */
    private function checkFilePhysical(PricelistFile $pricelistFile): array
    {
        $errors = [];
        $warnings = [];
        $info = [
            'original_filename' => $pricelistFile->filename,
            'file_size_bytes' => null,
            'file_size_human' => null,
            'mime_type' => null,
        ];

        $disk = Storage::disk('public');

        if (!$disk->exists($pricelistFile->filename)) {
            $errors[] = $this->buildError(PreProcessErrorCode::FILE_NOT_FOUND);
            return ['info' => $info, 'errors' => $errors, 'warnings' => $warnings];
        }

        $fullPath = $disk->path($pricelistFile->filename);
        $extension = strtolower(pathinfo($fullPath, PATHINFO_EXTENSION));

        if (!in_array($extension, self::ALLOWED_EXTENSIONS)) {
            $errors[] = $this->buildError(
                PreProcessErrorCode::INVALID_EXTENSION,
                "Kiterjesztés: '{$extension}', elfogadott: " . implode(', ', self::ALLOWED_EXTENSIONS)
            );
            return ['info' => $info, 'errors' => $errors, 'warnings' => $warnings];
        }

        $fileSize = $disk->size($pricelistFile->filename);
        $info['file_size_bytes'] = $fileSize;
        $info['file_size_human'] = $this->humanFileSize($fileSize);
        $info['mime_type'] = $disk->mimeType($pricelistFile->filename);

        if ($fileSize < self::MIN_FILE_SIZE_BYTES) {
            $errors[] = $this->buildError(PreProcessErrorCode::FILE_EMPTY, "Fájlméret: {$info['file_size_human']}");
        }

        if ($fileSize > self::MAX_FILE_SIZE_BYTES) {
            $warnings[] = $this->buildWarning(PreProcessErrorCode::FILE_TOO_LARGE, "Fájlméret: {$info['file_size_human']}");
        }

        return ['info' => $info, 'errors' => $errors, 'warnings' => $warnings];
    }

    /**
     * Oszlopstruktúra és fejléc validálása
     */
    private function validateColumnStructure(Worksheet $worksheet): array
    {
        $errors = [];
        $warnings = [];

        $expectedColumnCount = count($this->priceListService->getExcelFieldPointer());
        $highestColumn = $worksheet->getHighestDataColumn();
        $columnCount = Coordinate::columnIndexFromString($highestColumn);

        if ($columnCount < $expectedColumnCount) {
            $errors[] = $this->buildError(
                PreProcessErrorCode::COLUMN_COUNT_MISMATCH,
                "Kevés oszlop ({$columnCount}), elvárt legalább: {$expectedColumnCount}"
            );
        } elseif ($columnCount > $expectedColumnCount) {
            $warnings[] = $this->buildWarning(
                PreProcessErrorCode::COLUMN_EXTRA,
                "Többlet oszlopok ({$columnCount}), az első {$expectedColumnCount} kerül feldolgozásra."
            );
        }

        // Fejléc ellenőrzés (4. sor a specifikáció szerint)
        $headerRow = 4;
        $expectedHeaders = PriceListService::EXPECTED_HEADERS;
        $checkCount = min($columnCount, count($expectedHeaders));

        for ($col = 0; $col < $checkCount; $col++) {
            $cellValue = trim((string) $worksheet->getCellByColumnAndRow($col + 1, $headerRow)->getValue());
            $expectedValue = $expectedHeaders[$col] ?? null;

            if ($expectedValue !== null && $cellValue !== $expectedValue) {
                // Az oszlopnevek eltérése blocker hiba
                $errors[] = $this->buildError(
                    PreProcessErrorCode::HEADER_MISMATCH,
                    "A(z) " . ($col + 1) . ". oszlop fejléce '{$cellValue}' az elvárt '{$expectedValue}' helyett (a {$headerRow}. sorban)."
                );
            }
        }

        return ['errors' => $errors, 'warnings' => $warnings];
    }

    /**
     * Statisztikai adatok gyűjtése a munkalapról
     */
    private function collectStatistics(Worksheet $worksheet): array
    {
        $highestRow = $worksheet->getHighestDataRow();
        $highestColumn = $worksheet->getHighestDataColumn();
        $columnCount = Coordinate::columnIndexFromString($highestColumn);

        $dataRows = 0;
        $emptyRows = 0;

        // A 4. sort fejlécnek tekintjük, az 5. sortól indul az adat
        for ($row = 5; $row <= $highestRow; $row++) {
            $supplierProductNumber = $worksheet->getCellByColumnAndRow(1, $row)->getValue();
            if (!empty(trim((string) $supplierProductNumber))) {
                $dataRows++;
            } else {
                $emptyRows++;
            }
        }

        return [
            'total_rows' => max(0, $highestRow - 4), // 4 sor fejléc/leírás után
            'data_rows' => $dataRows,
            'empty_rows' => $emptyRows,
            'column_count' => $columnCount,
            'expected_column_count' => count($this->priceListService->getExcelFieldPointer()),
        ];
    }

    /**
     * Hiba rekord összeállítása
     */
    private function buildError(PreProcessErrorCode $code, ?string $customMessage = null): array
    {
        return [
            'code' => $code->value,
            'message' => $customMessage ?? $code->label(),
            'severity' => $code->severity(),
        ];
    }

    /**
     * Figyelmeztetés rekord összeállítása
     */
    private function buildWarning(PreProcessErrorCode $code, ?string $customMessage = null, array $extra = []): array
    {
        return array_merge([
            'code' => $code->value,
            'message' => $customMessage ?? $code->label(),
            'severity' => $code->severity(),
        ], $extra);
    }

    /**
     * Blocker típusú hiba keresése a hibalistában
     */
    private function hasBlockerError(array $errors): bool
    {
        foreach ($errors as $error) {
            if (($error['severity'] ?? '') === 'blocker') {
                return true;
            }
        }
        return false;
    }

    /**
     * Meta adatok mentése és a workflow lépés hibásra állítása
     */
    private function saveMetaAndFail(PricelistFile $pricelistFile, array $meta, float $startTime): void
    {
        $meta['completed_at'] = now()->toDateTimeString();
        $meta['duration_seconds'] = round(microtime(true) - $startTime, 2);

        $pricelistFile->update(['file_meta' => ['preprocess' => $meta]]);

        $firstBlocker = collect($meta['errors'])->firstWhere('severity', 'blocker');
        $failMessage = $firstBlocker['message'] ?? 'Előfeldolgozás sikertelen.';

        $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Preprocessing, 'failed', $failMessage);
    }

    /**
     * Fájlméret olvasható formátumban
     */
    private function humanFileSize(int $bytes): string
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        $i = 0;
        $size = (float) $bytes;

        while ($size >= 1024 && $i < count($units) - 1) {
            $size /= 1024;
            $i++;
        }

        return round($size, 1) . ' ' . $units[$i];
    }

    /**
     * Validálás (Validation) - Üzleti szabályok ellenőrzése és sorok rögzítése
     *
     * @param PricelistFile $pricelistFile
     * @return bool
     */
    public function validate(PricelistFile $pricelistFile): bool
    {
        $startTime = microtime(true);
        $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Validation, 'inprogress', 'Sorok beolvasása és mentése...', 0);

        try {
            // Korábbi sorok törlése az újraindíthatóság érdekében
            $pricelistFile->lines()->delete();

            $fullPath = Storage::disk('public')->path($pricelistFile->filename);
            $spreadsheet = IOFactory::load($fullPath);
            $worksheet = $spreadsheet->getActiveSheet();
            $highestRow = $worksheet->getHighestDataRow();
            $expectedHeaders = PriceListService::EXPECTED_HEADERS;

            $batchSize = 50;
            $dataToInsert = [];
            $processedCount = 0;
            $totalDataRows = $highestRow >= 5 ? ($highestRow - 4) : 0;

            if ($totalDataRows === 0) {
                throw new \Exception('A fájl nem tartalmaz feldolgozható adatsort az 5. sortól.');
            }

            for ($row = 5; $row <= $highestRow; $row++) {
                $rowData = [];
                $hasData = false;

                foreach ($expectedHeaders as $index => $label) {
                    $cell = $worksheet->getCellByColumnAndRow($index + 1, $row);
                    $value = $cell->getValue();

                    // Ha képlet, próbáljuk kiszámolni a megjelenített értéket
                    if (str_starts_with((string)$value, "=")) {
                        try {
                            // Megpróbáljuk kiszámítani a képletet
                            $value = $cell->getCalculatedValue();
                        } catch (\Exception $e) {
                            try {
                                // Ha a számítás nem sikerül (pl. hiányzó külső hivatkozás),
                                // próbáljuk a fájlban utoljára tárolt értéket (cache) lekérni
                                $value = $cell->getOldCalculatedValue();
                            } catch (\Exception $e2) {
                                // Ha nincs korábbi érték sem, marad a képlet szövege fallback-ként
                                \Illuminate\Support\Facades\Log::warning("Képlet számítási hiba a(z) {$row}. sorban: " . $e->getMessage());
                            }
                        }
                    }

                    $rowData[$label] = $value;
                    if (!empty(trim((string)$value))) {
                        $hasData = true;
                    }
                }

                // Csak akkor mentjük, ha a sor nem teljesen üres
                if ($hasData) {
                    $dataToInsert[] = [
                        'pricelist_file_id' => $pricelistFile->id,
                        'row_number' => $row,
                        'status' => PricelistFileLineStatusEnum::ok->value,
                        'payload' => json_encode($rowData),
                        'created_at' => now(),
                        'updated_at' => now(),
                        // A BaseAuditable trait miatt ezeket is érdemes lehet kitölteni,
                        // de a tömeges beszúrásnál (insert) nem futnak le a boot események.
                        'created_by' => auth()->id() ?? 1,
                    ];
                }

                $processedCount++;

                if (count($dataToInsert) >= $batchSize) {
                    PricelistFileLine::insert($dataToInsert);
                    $dataToInsert = [];

                    $percentage = round(($processedCount / $totalDataRows) * 100);
                    $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Validation, 'inprogress', "Sorok mentése ({$processedCount}/{$totalDataRows})...", $percentage);
                }
            }

            if (!empty($dataToInsert)) {
                PricelistFileLine::insert($dataToInsert);
            }

            $this->runBusinessValidation($pricelistFile);

            $duration = round(microtime(true) - $startTime, 2);

            // Ellenőrizzük, vannak-e hibás sorok
            $hasErrors = $pricelistFile->lines()->where('status', PricelistFileLineStatusEnum::error)->exists();

            if ($hasErrors) {
                $this->updateStepStatus(
                    $pricelistFile,
                    PricelistWorkflowStep::Validation,
                    'failed',
                    "befejeződött, de hibás sorokat találtunk. Kérjük, javítsa a hibákat a továbblépéshez!",
                    100
                );
                return false;
            }

            $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Validation, 'completed', "Sikeres validálás ({$processedCount} sor, {$duration} mp).", 100);

            // A validálás végén átadjuk a lépést a jóváhagyásnak (Approval)
            $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Approval, 'inprogress');

            return true;
        } catch (\Exception $e) {
            Log::error('Pricelist validation error: ' . $e->getMessage());
            $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Validation, 'failed', $e->getMessage());
            throw $e;
        }
    }

    /**
     * Szöveg normalizálása az összehasonlításhoz (mb_strtoupper, trim, speciális karakterek kezelése)
     */
    protected function normalizeForComparison(string $text): string
    {
        // 1. Kis/Nagybetű érzéketlenség UTF-8 támogatással
        $text = mb_strtoupper(trim($text), 'UTF-8');

        // 2. Szeparátorok egységesítése (perjel és alulvonás cseréje)
        // A hibaüzenetben látható alulvonás és a felhasználó által említett perjel miatt
        $text = str_replace(['/', '_'], ' ', $text);

        // 3. Felesleges szóközök eltávolítása (ha a csere után több szóköz maradt)
        $text = preg_replace('/\s+/', ' ', $text);

        return trim($text);
    }

    /**
     * Termékcsoport lookup tábla felépítése az adatbázisból (hierarchia útvonala -> id)
     */
    protected function getProductGroupLookupMap(): array
    {
        $allGroups = (new \App\Models\ProductGroup)->allWithPath(3);
        $groupLookup = [];

        foreach ($allGroups as $group) {
            $parentsNames = $group['parentsNames'] ?? [];

            // Technikai gyökér elhagyása (0. szint, pl. "Főcsoportok")
            if (isset($group['depth']) && $group['depth'] > 0 && !empty($parentsNames)) {
                array_shift($parentsNames);
            }

            $pathParts = array_merge($parentsNames, [$group['name']]);
            $normalizedParts = array_map(fn($v) => $this->normalizeForComparison((string)$v), $pathParts);
            $pathKey = implode('|', $normalizedParts);
            $groupLookup[$pathKey] = $group['id'];
        }

        return $groupLookup;
    }

    /**
     * Gyártó lookup tábla felépítése (név -> id)
     */
    protected function getProducerLookupMap(): array
    {
        return \App\Models\Producer::all()
            ->keyBy(fn($p) => $this->normalizeForComparison($p->name))
            ->map->id
            ->toArray();
    }

    /**
     * Gyártó ID -> név mapping
     */
    protected function getProducerIdMap(): array
    {
        return \App\Models\Producer::pluck('name', 'id')->toArray();
    }

    /**
     * Termékcsoport ID -> útvonal mapping
     */
    protected function getProductGroupIdMap(): array
    {
        $allGroups = (new \App\Models\ProductGroup)->allWithPath();
        $idMap = [];

        foreach ($allGroups as $group) {
            $parentsNames = $group['parentsNames'] ?? [];

            if (isset($group['depth']) && $group['depth'] > 0 && !empty($parentsNames)) {
                array_shift($parentsNames);
            }

            $pathParts = array_merge($parentsNames, [$group['name']]);
            $idMap[$group['id']] = implode(' > ', $pathParts);
        }

        return $idMap;
    }

    /**
     * Termék lookup tábla felépítése a beszállítóhoz (cikkszám -> id)
     */
    protected function getProductLookupMap(int $supplierId): array
    {
        return \App\Models\Product::where('supplier_id', $supplierId)
            ->whereNotNull('supplierProductNumber')
            ->get()
            ->keyBy('supplierProductNumber')
            ->toArray();
    }

    /**
     * Termékcsoport útvonal összeállítása a payload-ból
     */
    protected function getRowPathFromPayload(array $payload): string
    {
        $pathComponents = [
            $payload[PriceListService::EXPECTED_HEADERS[1]] ?? null,
            $payload[PriceListService::EXPECTED_HEADERS[2]] ?? null,
            $payload[PriceListService::EXPECTED_HEADERS[3]] ?? null
        ];

        return collect($pathComponents)
            ->filter(fn($v) => !empty(trim((string)$v)))
            ->map(fn($v) => $this->normalizeForComparison((string)$v))
            ->implode('|');
    }

    /**
     * Termékcsoport validáció egy sorra
     */
    protected function validateProductGroup(string $rowPath, array $groupLookup): ?string
    {
        if (empty($rowPath)) {
            return "Hiányzó termékcsoport adatok.";
        }

        if (!isset($groupLookup[$rowPath])) {
            return "Ismeretlen termékcsoport útvonal: $rowPath";
        }

        return null;
    }

    /**
     * Mértékegység validáció egy sorra
     */
    protected function validateUnit(array $payload): ?string
    {
        $unitValue = $payload[PriceListService::EXPECTED_HEADERS[7]] ?? null;

        if (empty(trim((string)$unitValue))) {
            return "Hiányzó mértékegység.";
        }

        $normalizedUnit = mb_strtolower(trim((string)$unitValue));
        if (!PricelistUnitEnum::tryFrom($normalizedUnit)) {
            return "Érvénytelen mértékegység: $unitValue. Elfogadott: " . implode(', ', PricelistUnitEnum::values());
        }

        return null;
    }

    /**
     * Legkisebb eladási egység validáció egy sorra
     */
    protected function validateSellerUnit(array $payload): ?string
    {
        $sellerUnitValue = $payload[PriceListService::EXPECTED_HEADERS[9]] ?? null;

        if (empty(trim((string)$sellerUnitValue))) {
            return "Hiányzó legkisebb eladási egység.";
        }

        $normalizedUnit = mb_strtolower(trim((string)$sellerUnitValue));
        if (!PricelistSellerUnitEnum::tryFrom($normalizedUnit)) {
            return "Érvénytelen legkisebb eladási egység: $sellerUnitValue. Elfogadott: " . implode(', ', PricelistSellerUnitEnum::values());
        }

        return null;
    }

    /**
     * Mennyiségi egység validáció egy sorra
     */
    protected function validateAmountUnit(array $payload): ?string
    {
        $amountUnitValue = $payload[PriceListService::EXPECTED_HEADERS[11]] ?? null;

        if (empty(trim((string)$amountUnitValue))) {
            return "Hiányzó mennyiségi egység.";
        }

        $normalizedUnit = mb_strtolower(trim((string)$amountUnitValue));
        if (!PricelistSellerUnitEnum::tryFrom($normalizedUnit)) {
            return "Érvénytelen mennyiségi egység: $amountUnitValue. Elfogadott: " . implode(', ', PricelistSellerUnitEnum::values());
        }

        return null;
    }

    /**
     * ÁFA % validáció egy sorra
     */
    protected function validateVat(array $payload): ?string
    {
        $vatValue = $payload[PriceListService::EXPECTED_HEADERS[12]] ?? null;

        if ($vatValue === null || (is_string($vatValue) && trim($vatValue) === '')) {
            return "Hiányzó ÁFA %.";
        }

        // Kinyerjük a numerikus értéket, kezelve a százalékjelet, szóközöket és a tizedesvesszőt
        $numericValue = (float) str_replace(['%', ' ', ','], ['', '', '.'], (string)$vatValue);

        // Ha az érték 0 és 1 közé esik (pl. 0.05, 0.27), akkor valószínűleg Excel százalék formátum
        // Ebben az esetben megszorozzuk 100-zal, hogy megkapjuk az egész értéket (pl. 5, 27)
        if ($numericValue > 0 && $numericValue <= 1) {
            $numericValue *= 100;
        }

        $finalVat = (int) round($numericValue);

        if (!PricelistVatEnum::tryFrom($finalVat)) {
            return "Érvénytelen ÁFA %: $vatValue. Elfogadott: " . implode(', ', PricelistVatEnum::labels());
        }

        return null;
    }

    /**
     * Gyártó validáció egy sorra
     */
    protected function validateProducer(array $payload, array $producerLookup): ?string
    {
        $producerName = $payload[PriceListService::EXPECTED_HEADERS[8]] ?? null;

        if (empty(trim((string)$producerName))) {
            return "Hiányzó gyártó megnevezés.";
        }

        $normalizedName = $this->normalizeForComparison((string)$producerName);

        if (!isset($producerLookup[$normalizedName])) {
            return "NEW_PRODUCER";
        }

        return null;
    }

    /**
     * Üzleti validáció futtatása a mentett sorokon (Batch feldolgozás)
     *
     * @param PricelistFile $pricelistFile
     */
    protected function runBusinessValidation(PricelistFile $pricelistFile): void
    {
        $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Validation, 'inprogress', 'Üzleti validálás...', 100);

        $groupLookup = $this->getProductGroupLookupMap();
        $producerLookup = $this->getProducerLookupMap();
        $productLookup = $this->getProductLookupMap($pricelistFile->supplier_id);

        $producerIdToName = $this->getProducerIdMap();
        $groupIdToPath = $this->getProductGroupIdMap();

        $pricelistFile->lines()->chunk(1000, function ($lines) use ($groupLookup, $producerLookup, $productLookup, $producerIdToName, $groupIdToPath) {
            $updates = [];

            foreach ($lines as $line) {
                $payload = $line->payload;
                $validationMessages = [];

                // 1. Kötelező mezők ellenőrzése (0-12 és 14)
                $mandatoryIndexes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14];
                foreach ($mandatoryIndexes as $mIndex) {
                    $mLabel = PriceListService::EXPECTED_HEADERS[$mIndex];
                    $mValue = $payload[$mLabel] ?? null;
                    if ($mValue === null || (is_string($mValue) && trim($mValue) === '')) {
                        // Megfelelő kulcsot választunk, hogy a specifikus validátorok felülírhassák
                        $mKey = match($mIndex) {
                            7 => 'product_unit',
                            8 => 'producer',
                            9 => 'seller_unit',
                            11 => 'amount_unit',
                            12 => 'vat',
                            default => 'mandatory_' . $mIndex
                        };
                        $validationMessages[$mKey] = "Hiányzó kötelező mező: $mLabel";
                    }
                }

                // 2. Hooreyca specifikus mezők függősége (16, 17, 18, 19)
                $hooreycaIndexes = [16, 17, 18, 19];
                $hasAnyHooreyca = false;
                $hooreycaData = [];
                foreach ($hooreycaIndexes as $hIndex) {
                    $hLabel = PriceListService::EXPECTED_HEADERS[$hIndex];
                    $hValue = $payload[$hLabel] ?? null;
                    if ($hValue !== null && !(is_string($hValue) && trim($hValue) === '')) {
                        $hasAnyHooreyca = true;
                        $hooreycaData[$hIndex] = $hValue;
                    } else {
                        $hooreycaData[$hIndex] = null;
                    }
                }

                if ($hasAnyHooreyca) {
                    foreach ($hooreycaIndexes as $hIndex) {
                        if ($hooreycaData[$hIndex] === null) {
                            $hLabel = PriceListService::EXPECTED_HEADERS[$hIndex];
                            $validationMessages['hooreyca_' . $hIndex] = "Hiányzó kötelező mező (Hooreyca adatok jelenléte miatt): $hLabel";
                        }
                    }
                }

                // Termék ID kinyerése (cikkszám alapján)
                $sku = (string)($payload[PriceListService::EXPECTED_HEADERS[0]] ?? '');
                $existingProduct = $productLookup[$sku] ?? null;
                $productId = $existingProduct['id'] ?? null;

                // Termékcsoport útvonal és ID kinyerése
                $rowPath = $this->getRowPathFromPayload($payload);
                $groupId = $groupLookup[$rowPath] ?? null;

                // Gyártó ID kinyerése
                $producerName = $payload[PriceListService::EXPECTED_HEADERS[8]] ?? null;
                $producerId = $producerLookup[$this->normalizeForComparison((string)$producerName)] ?? null;

                // Különálló validációk meghívása
                if ($groupError = $this->validateProductGroup($rowPath, $groupLookup)) {
                    $validationMessages['product_group'] = $groupError;
                }

                $hasNewProducer = false;
                if ($producerError = $this->validateProducer($payload, $producerLookup)) {
                    if ($producerError === 'NEW_PRODUCER') {
                        $validationMessages['producer'] = "Új gyártó: " . ($payload[PriceListService::EXPECTED_HEADERS[8]] ?? '');
                        $hasNewProducer = true;
                    } else {
                        $validationMessages['producer'] = $producerError;
                    }
                }

                if ($unitError = $this->validateUnit($payload)) {
                    $validationMessages['product_unit'] = $unitError;
                }

                if ($sellerUnitError = $this->validateSellerUnit($payload)) {
                    $validationMessages['seller_unit'] = $sellerUnitError;
                }

                if ($amountUnitError = $this->validateAmountUnit($payload)) {
                    $validationMessages['amount_unit'] = $amountUnitError;
                }

                if ($vatError = $this->validateVat($payload)) {
                    $validationMessages['vat'] = $vatError;
                }

                // Státusz meghatározása
                $hasError = false;

                foreach ($validationMessages as $key => $msg) {
                    if (str_contains($msg, 'Hiányzó') || str_contains($msg, 'Ismeretlen') || str_contains($msg, 'Érvénytelen')) {
                        $hasError = true;
                        break;
                    }
                }

                $status = PricelistFileLineStatusEnum::ok;
                $diff = [];

                if ($hasError) {
                    $status = PricelistFileLineStatusEnum::error;
                } elseif ($productId === null) {
                    // Ha nincs meglévő termék -> Új termék
                    $status = PricelistFileLineStatusEnum::new_product;
                } else {
                    // Meglévő termék esetén nézzük a módosulásokat
                    $isUpdated = false;

                    // 1. Gyártó módosulás
                    $oldProducerId = (int)($existingProduct['producer_id'] ?? 0);
                    if ($hasNewProducer) {
                        $isUpdated = true;
                        $diff['producer_id'] = [
                            'old' => $oldProducerId,
                            'new' => $producerId,
                            'old_label' => $producerIdToName[$oldProducerId] ?? ($oldProducerId > 0 ? "Ismeretlen gyártó ($oldProducerId)" : 'N/A'),
                            'new_label' => $producerName,
                            'label' => PriceListService::EXPECTED_HEADERS[8]
                        ];
                    } elseif ($producerId !== null && $oldProducerId !== (int)$producerId) {
                        $isUpdated = true;
                        $diff['producer_id'] = [
                            'old' => $oldProducerId,
                            'new' => (int)$producerId,
                            'old_label' => $producerIdToName[$oldProducerId] ?? ($oldProducerId > 0 ? "Ismeretlen gyártó ($oldProducerId)" : 'N/A'),
                            'new_label' => $producerIdToName[(int)$producerId] ?? ($producerId > 0 ? "Ismeretlen gyártó ($producerId)" : 'N/A'),
                            'label' => PriceListService::EXPECTED_HEADERS[8]
                        ];
                    }

                    // 2. Termékcsoport módosulás
                    $oldGroupId = (int)($existingProduct['product_group_id'] ?? 0);
                    if ($groupId !== null && $oldGroupId !== (int)$groupId) {
                        $isUpdated = true;
                        $oldPath = $groupIdToPath[$oldGroupId] ?? null;
                        $newPath = $groupIdToPath[(int)$groupId] ?? null;

                        $diff['product_group_id'] = [
                            'old' => $oldGroupId,
                            'new' => (int)$groupId,
                            'old_label' => $oldPath ?? ($oldGroupId > 0 ? "Ismeretlen csoport ($oldGroupId)" : 'N/A'),
                            'new_label' => $newPath ?? ($groupId > 0 ? "Ismeretlen csoport ($groupId)" : 'N/A'),
                            'label' => PriceListService::EXPECTED_HEADERS[1]
                        ];
                    }

                    // 3. Általános mezők összehasonlítása
                    foreach (self::PRODUCT_FIELD_MAP as $modelField => $config) {
                        $excelValue = $payload[$config['header']] ?? null;
                        $currentValue = $existingProduct[$modelField] ?? null;

                        // Típuskonverzió és normalizálás az összehasonlításhoz
                        if ($config['type'] === 'float') {
                            $excelValue = is_string($excelValue)
                                ? (float)str_replace([' ', ','], ['', '.'], $excelValue)
                                : (float)$excelValue;

                            // ÁFA speciális kezelése: 0.05 -> 5
                            if ($modelField === 'vat') {
                                if ($excelValue > 0 && $excelValue <= 1) {
                                    $excelValue *= 100;
                                }
                                $excelValue = (float) round($excelValue);
                            }

                            $currentValue = (float)$currentValue;
                        }

                        if ($excelValue != $currentValue) {
                            $diff[$modelField] = [
                                'old' => $currentValue,
                                'new' => $excelValue,
                                'label' => $config['header']
                            ];
                            $isUpdated = true;
                        }
                    }

                    if ($isUpdated) {
                        $status = PricelistFileLineStatusEnum::updated;
                    }

                    // 4. Ár módosulás (mindig bekerül a diff-be ha van változás, de nem feltétlenül váltja ki az 'updated' státuszt, ha az árat külön kezeljük)
                    // Megjegyzés: Az áráltozás önmagában is lehet 'updated', de sokszor az árlista import lényege az árváltozás.
                    $oldPriceExcel = $payload[PriceListService::EXPECTED_HEADERS[13]] ?? null;
                    $newPriceExcel = $payload[PriceListService::EXPECTED_HEADERS[14]] ?? null;

                    if ($oldPriceExcel !== null && $newPriceExcel !== null) {
                        // Szóközök és vesszők egységes kezelése string típus esetén
                        $oldPrice = (float)str_replace([' ', ','], ['', '.'], (string)$oldPriceExcel);
                        $newPrice = (float)str_replace([' ', ','], ['', '.'], (string)$newPriceExcel);

                        if ($oldPrice != $newPrice) {
                            $diff['price'] = [
                                'old' => $oldPrice,
                                'new' => $newPrice,
                                'label' => PriceListService::EXPECTED_HEADERS[14]
                            ];
                        }
                    }
                }

                $updates[] = [
                    'id' => $line->id,
                    'pricelist_file_id' => $line->pricelist_file_id,
                    'row_number' => $line->row_number,
                    'status' => $status->value,
                    'product_id' => $productId,
                    'product_group_id' => $groupId,
                    'producer_id' => $producerId,
                    'payload' => json_encode($line->payload),
                    'validation_messages' => json_encode($validationMessages),
                    'diff' => json_encode($diff),
                    'created_at' => $line->created_at->toDateTimeString(),
                    'updated_at' => now()->toDateTimeString(),
                ];
            }

            // Kötegelt frissítés (upsert) a teljesítmény optimalizálásához
            if (!empty($updates)) {
                PricelistFileLine::upsert(
                    $updates,
                    ['id'], // Egyedi azonosító
                    ['status', 'validation_messages', 'diff', 'product_id', 'product_group_id', 'producer_id', 'updated_at'] // Frissítendő mezők
                );
            }
        });
    }

    /**
     * Végrehajtás (Execution) - Módosítások tényleges alkalmazása tranzakcióban
     *
     * @param PricelistFile $pricelistFile
     * @return bool
     */
    public function execute(PricelistFile $pricelistFile): bool
    {
        $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Execution, 'inprogress', 'Árlista frissítése...', 0);

        DB::beginTransaction();
        try {
            // TODO: Tényleges importálás végrehajtása
            // $this->priceListService->importPriceList(...);
            sleep(1); // Szimuláció

            $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Execution, 'completed', 'Sikeresen befejezve.', 100);

            DB::commit();
            $pricelistFile->update([
                'status' => PricelistFileStatusEnum::done,
            ]);
            return true;
        } catch (\Exception $e) {
            DB::rollBack();
            Log::error('Pricelist execution error: ' . $e->getMessage());
            $this->updateStepStatus($pricelistFile, PricelistWorkflowStep::Execution, 'failed', $e->getMessage());
            return false;
        }
    }
}
