<?php

namespace App\Services;

use App\Models\Tenant\Part;
use App\Models\Tenant\Purchase;
use App\Models\Tenant\PurchaseItem;
use App\Models\Tenant\Supplier;
use App\Models\Tenant\SupplierInvoiceMapping;
use Illuminate\Support\Facades\DB;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;

/**
 * Parses XLSX / CSV files coming from suppliers and turns them into
 * Purchase drafts. Mapping (column letters → field) is stored per supplier
 * so the second import becomes instant.
 *
 * Three steps:
 *   1. headersPreview($path)        — first 5 rows + detected column letters
 *   2. preview($path, $mapping)     — parse all rows + classify each as
 *                                     found (article matches part) / new
 *                                     (article exists in file but not in DB) /
 *                                     no_article (line has data but no article col)
 *   3. import($supplier, $rows)     — create Purchase + PurchaseItems; auto-creates
 *                                     Parts for "new" rows when create_new = true
 */
class ExcelInvoiceImportService
{
    /** Read first $maxRows of a spreadsheet for the wizard preview. */
    public function headersPreview(string $absPath, int $maxRows = 5): array
    {
        $sheet = $this->loadSheet($absPath);
        $highestColumn = $sheet->getHighestColumn();
        $highestColIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highestColumn);
        $cols = [];
        for ($i = 1; $i <= min($highestColIndex, 20); $i++) {
            $cols[] = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i);
        }
        $rows = [];
        for ($r = 1; $r <= $maxRows; $r++) {
            $row = [];
            foreach ($cols as $col) {
                $row[$col] = (string) ($sheet->getCell("$col$r")->getValue() ?? '');
            }
            $rows[] = $row;
        }
        return ['columns' => $cols, 'rows' => $rows];
    }

    /**
     * Parse the file according to $mapping and classify each line.
     *
     * Mapping keys:
     *   article_col, name_col, qty_col, price_col, brand_col (optional),
     *   header_row (1-based row number to skip), sheet_name (optional).
     */
    public function preview(string $absPath, array $mapping): array
    {
        $sheet = $this->loadSheet($absPath, $mapping['sheet_name'] ?? null);
        $headerRow = (int) ($mapping['header_row'] ?? 1);
        $highestRow = $sheet->getHighestRow();

        $rows = [];
        $articlesInFile = [];

        for ($r = $headerRow + 1; $r <= $highestRow; $r++) {
            $article = $this->cellString($sheet, $mapping['article_col'] ?? null, $r);
            $name = $this->cellString($sheet, $mapping['name_col'] ?? null, $r);
            $brand = $this->cellString($sheet, $mapping['brand_col'] ?? null, $r);
            $qty = $this->cellDecimal($sheet, $mapping['qty_col'] ?? null, $r);
            $price = $this->cellDecimal($sheet, $mapping['price_col'] ?? null, $r);

            // Skip totally empty lines
            if ($article === '' && $name === '' && $qty <= 0) continue;

            $status = 'no_article';
            $partId = null;
            if ($article !== '') {
                $articlesInFile[] = $article;
            }

            $rows[] = [
                'row' => $r,
                'article' => $article,
                'name' => $name,
                'brand' => $brand,
                'qty' => $qty,
                'price' => $price,
                'status' => $status,
                'part_id' => $partId,
            ];
        }

        // Single batch query for all articles → status
        if (! empty($articlesInFile)) {
            $existing = Part::whereIn('article', array_unique($articlesInFile))->get()->keyBy('article');
            foreach ($rows as &$row) {
                if ($row['article'] === '') continue;
                if ($existing->has($row['article'])) {
                    $row['status'] = 'found';
                    $row['part_id'] = $existing[$row['article']]->id;
                } else {
                    $row['status'] = 'new';
                }
            }
            unset($row);
        }

        return [
            'rows' => $rows,
            'summary' => [
                'total' => count($rows),
                'found' => count(array_filter($rows, fn ($r) => $r['status'] === 'found')),
                'new' => count(array_filter($rows, fn ($r) => $r['status'] === 'new')),
                'no_article' => count(array_filter($rows, fn ($r) => $r['status'] === 'no_article')),
            ],
        ];
    }

    /**
     * Create a draft Purchase + items from the rows. Rows with status='new'
     * automatically get a Part created if $createNew is true.
     */
    public function import(Supplier $supplier, array $rows, bool $createNew = true): Purchase
    {
        return DB::transaction(function () use ($supplier, $rows, $createNew) {
            $purchase = Purchase::create([
                'supplier_id' => $supplier->id,
                'number' => $this->generatePurchaseNumber($supplier->company_id),
                'order_date' => today(),
                'status' => 'ordered',
                'total' => 0,
                'notes' => 'Auto-import Excel/CSV',
            ]);
            $total = 0;
            foreach ($rows as $row) {
                $partId = $row['part_id'] ?? null;
                if (! $partId && $row['status'] === 'new' && $createNew && $row['article'] !== '') {
                    $part = Part::create([
                        'name' => $row['name'] ?: $row['article'],
                        'article' => $row['article'],
                        'brand' => $row['brand'] ?? null,
                        'buy_price' => $row['price'],
                        'preferred_supplier_id' => $supplier->id,
                    ]);
                    $partId = $part->id;
                }
                PurchaseItem::create([
                    'purchase_id' => $purchase->id,
                    'part_id' => $partId,
                    'name' => $row['name'] ?: $row['article'],
                    'article' => $row['article'],
                    'qty' => $row['qty'] ?: 1,
                    'qty_received' => 0,
                    'buy_price' => $row['price'],
                    'total' => round(($row['qty'] ?: 1) * $row['price'], 2),
                ]);
                $total += ($row['qty'] ?: 1) * $row['price'];
            }
            $purchase->update(['total' => round($total, 2)]);
            return $purchase;
        });
    }

    /**
     * Save mapping config for a supplier so subsequent imports skip the wizard.
     */
    public function rememberMapping(Supplier $supplier, array $mapping, ?string $sampleFileName = null): SupplierInvoiceMapping
    {
        return SupplierInvoiceMapping::updateOrCreate(
            ['supplier_id' => $supplier->id],
            [
                'mapping_config' => $mapping,
                'sample_file_name' => $sampleFileName,
                'last_used_at' => now(),
            ]
        );
    }

    public function rememberedMappingFor(Supplier $supplier): ?array
    {
        $m = SupplierInvoiceMapping::where('supplier_id', $supplier->id)->first();
        return $m?->mapping_config;
    }

    private function loadSheet(string $absPath, ?string $sheetName = null)
    {
        $reader = IOFactory::createReaderForFile($absPath);
        $reader->setReadDataOnly(true);
        $spreadsheet = $reader->load($absPath);
        return $sheetName ? $spreadsheet->getSheetByName($sheetName) ?? $spreadsheet->getActiveSheet() : $spreadsheet->getActiveSheet();
    }

    private function cellString($sheet, ?string $col, int $row): string
    {
        if (! $col) return '';
        $value = $sheet->getCell("$col$row")->getValue();
        return trim((string) ($value ?? ''));
    }

    private function cellDecimal($sheet, ?string $col, int $row): float
    {
        if (! $col) return 0.0;
        $value = $sheet->getCell("$col$row")->getValue();
        if ($value === null || $value === '') return 0.0;
        // Normalize "1 234,56" / "1,234.56" → 1234.56
        // PCRE doesn't support \u{XXXX} — use \x{00A0} (non-breaking space) instead
        $clean = preg_replace('/[\s\x{00A0}]/u', '', (string) $value);
        $clean = str_replace(',', '.', $clean);
        // remove duplicate dots if any
        $parts = explode('.', $clean);
        if (count($parts) > 2) {
            $clean = implode('', array_slice($parts, 0, -1)) . '.' . end($parts);
        }
        return (float) $clean;
    }

    private function generatePurchaseNumber(int $companyId): string
    {
        $year = date('Y');
        $last = Purchase::where('company_id', $companyId)
            ->where('number', 'like', "P-$year-%")
            ->orderByDesc('id')->first();
        $next = 1;
        if ($last && preg_match('/P-\d{4}-(\d+)$/', $last->number, $m)) {
            $next = (int) $m[1] + 1;
        }
        return sprintf('P-%s-%04d', $year, $next);
    }
}
