<?php

declare(strict_types=1);

namespace App\Services;

use App\Repositories\ArticleRepository;
use App\Repositories\FamilleArticleRepository;
use App\Repositories\MarqueArticleRepository;
use App\Repositories\UniteArticleRepository;
use PhpOffice\PhpSpreadsheet\IOFactory;

final class ArticleService extends BaseService
{
    public function __construct(
        private readonly ArticleRepository $articles = new ArticleRepository(),
        private readonly ArticlePricingService $pricing = new ArticlePricingService()
    ) {
    }

    public function listActive(): array { return $this->articles->allActive(); }
    public function listAll(): array { return $this->articles->all(); }

    public function find(int $id): array
    {
        $article = $this->articles->find($id);
        if (!$article) throw new \RuntimeException('Article introuvable.');
        return $article;
    }

    public function formData(?int $id = null, bool $duplicate = false): array
    {
        $article = $id ? $this->find($id) : [
            'reference' => '', 'designation' => '', 'conditionnement' => '', 'unite' => 'unité',
            'prix_achat_ht' => '0.00', 'coefficient' => '1.000', 'prix_vente_ht' => '0.00',
            'taux_tva' => '20.00', 'prix_vente_ttc' => '0.00', 'marge_dh' => '0.00',
            'marge_pourcentage' => '0.00', 'stock_minimum' => '0.00', 'actif' => 1,
        ];
        if ($duplicate) {
            unset($article['id']);
            $article['reference'] = '';
            $article['designation'] = 'Copie de ' . (string)($article['designation'] ?? '');
            $article['actif'] = 1;
        }
        return [
            'article' => $article,
            'familles' => (new FamilleArticleRepository())->all(),
            'marques' => (new MarqueArticleRepository())->all(),
            'unites' => (new UniteArticleRepository())->allActive(),
            'fournisseurs' => (new FournisseurService())->listActive(),
        ];
    }

    public function validate(array $data, ?int $excludeId = null): array
    {
        $errors = [];
        $reference = trim((string)($data['reference'] ?? ''));
        $designation = trim((string)($data['designation'] ?? ''));
        if ($reference === '') $errors['reference'] = 'La référence est obligatoire.';
        elseif (mb_strlen($reference) > 50) $errors['reference'] = 'La référence ne peut pas dépasser 50 caractères.';
        elseif ($this->articles->referenceExists($reference, $excludeId)) $errors['reference'] = 'Cette référence existe déjà.';
        if ($designation === '') $errors['designation'] = 'La désignation est obligatoire.';
        elseif (mb_strlen($designation) > 255) $errors['designation'] = 'La désignation ne peut pas dépasser 255 caractères.';
        foreach (['prix_achat_ht' => 'Prix d’achat HT', 'coefficient' => 'Coefficient', 'prix_vente_ht' => 'Prix de vente HT', 'taux_tva' => 'TVA', 'stock_minimum' => 'Stock minimum'] as $field => $label) {
            $value = $this->number($data[$field] ?? 0);
            if ($value < 0) $errors[$field] = $label . ' ne peut pas être négatif.';
        }
        if ($this->number($data['taux_tva'] ?? 0) > 100) $errors['taux_tva'] = 'Le taux de TVA ne peut pas dépasser 100 %.';
        return $errors;
    }

    public function create(array $data): int
    {
        $errors = $this->validate($data);
        if ($errors) throw new \InvalidArgumentException(implode(' ', $errors));
        return $this->articles->create($this->normalize($data));
    }

    public function update(int $id, array $data): void
    {
        if ($id <= 0) throw new \RuntimeException('Identifiant article invalide.');
        $this->find($id);
        $errors = $this->validate($data, $id);
        if ($errors) throw new \InvalidArgumentException(implode(' ', $errors));
        $this->articles->update($id, $this->normalize($data));
    }

    public function setActive(int $id, bool $active): void
    {
        if ($id <= 0) throw new \RuntimeException('Identifiant article invalide.');
        $this->find($id);
        $this->articles->setActive($id, $active);
    }

    private function normalize(array $data): array
    {
        $pricing = $this->pricing->calculate([
            'prix_achat_ht' => $data['prix_achat_ht'] ?? 0,
            'coefficient' => $data['coefficient'] ?? 0,
            'prix_vente_ht' => $data['prix_vente_ht'] ?? 0,
            'taux_tva' => $data['taux_tva'] ?? 20,
        ]);
        return array_merge($data, $pricing, [
            'reference' => trim((string)($data['reference'] ?? '')),
            'designation' => trim((string)($data['designation'] ?? '')),
            'stock_minimum' => max(0, $this->number($data['stock_minimum'] ?? 0)),
            'actif' => isset($data['actif']) ? 1 : 0,
        ]);
    }

    public function updatePurchasing(int $id, array $data): void
    {
        if ($id <= 0) throw new \RuntimeException('Identifiant article invalide.');
        $article = $this->find($id);
        $pricing = $this->pricing->calculate([
            'prix_achat_ht' => $data['prix_achat_ht'] ?? $article['prix_achat_ht'] ?? 0,
            'coefficient' => $data['coefficient'] ?? $article['coefficient'] ?? 0,
            'prix_vente_ht' => $data['prix_vente_ht'] ?? $article['prix_vente_ht'] ?? 0,
            'taux_tva' => $data['taux_tva'] ?? $article['taux_tva'] ?? 20,
        ]);
        $this->articles->updatePurchasing($id, [
            'fournisseur_principal_id' => $data['fournisseur_principal_id'] ?? '',
            'reference_fournisseur' => $data['reference_fournisseur'] ?? '',
            'date_dernier_achat' => $data['date_dernier_achat'] ?? '',
            ...$pricing,
        ]);
    }

    public function previewImportFile(array $file): array
    {
        if (!class_exists(IOFactory::class)) throw new \RuntimeException('PhpSpreadsheet n’est pas installé. Lancez composer update dans le dossier du projet.');
        if (empty($file['tmp_name'])) throw new \RuntimeException('Veuillez sélectionner un fichier Excel.');
        $extension = strtolower(pathinfo((string)($file['name'] ?? ''), PATHINFO_EXTENSION));
        if (!in_array($extension, ['xlsx', 'xls'], true)) throw new \RuntimeException('Le fichier doit être au format .xlsx ou .xls.');
        $storageDir = dirname(__DIR__, 2) . '/storage/imports';
        if (!is_dir($storageDir)) mkdir($storageDir, 0775, true);
        $target = $storageDir . '/articles_' . date('Ymd_His') . '.' . $extension;
        if (!move_uploaded_file((string)$file['tmp_name'], $target)) throw new \RuntimeException('Impossible de déplacer le fichier importé.');
        return ['file' => $target, 'rows' => $this->readExcel($target)];
    }

    public function readExcel(string $file): array
    {
        $spreadsheet = IOFactory::load($file);
        $data = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
        if (count($data) < 2) return [];
        $headers = array_shift($data);
        $map = $this->mapHeaders($headers) + ['reference'=>'A','designation'=>'B','prix_achat_ht'=>'C','coefficient'=>'D','prix_vente_ht'=>'E','taux_tva'=>'F'];
        $rows = [];
        foreach ($data as $lineNumber => $row) {
            $prixAchat = $this->number($this->cell($row, $map, 'prix_achat_ht'));
            $coefficient = $this->number($this->cell($row, $map, 'coefficient')) ?: 1.0;
            $prixVente = $this->number($this->cell($row, $map, 'prix_vente_ht'));
            $tauxTva = $this->tva($this->cell($row, $map, 'taux_tva'));
            $pricing = $this->pricing->calculate([
                'prix_achat_ht' => $prixAchat,
                'coefficient' => $coefficient,
                'prix_vente_ht' => $prixVente,
                'taux_tva' => $tauxTva,
            ]);
            $article = array_merge([
                'reference' => $this->text($this->cell($row, $map, 'reference')),
                'designation' => $this->text($this->cell($row, $map, 'designation')),
                'unite' => 'unité', 'stock_minimum' => 0, 'errors' => [],
            ], $pricing);
            if ($article['reference'] === '' && $article['designation'] === '') continue;
            if ($article['reference'] === '') $article['errors'][] = 'Référence manquante';
            if ($article['designation'] === '') $article['errors'][] = 'Désignation manquante';
            $article['line'] = (int)$lineNumber;
            $rows[] = $article;
        }
        return $rows;
    }

    public function importRows(array $rows, string $mode): array
    {
        return $this->articles->importMany(array_values(array_filter($rows, static fn(array $row): bool => empty($row['errors']))), $mode);
    }

    private function mapHeaders(array $headers): array
    {
        $wanted = [
            'reference'=>['reference','référence','ref','réf'], 'designation'=>['description','designation','désignation','libelle','libellé'],
            'prix_achat_ht'=>['puaht','p.u.a.ht','p u a ht','pua ht','p.u.a ht','prixachatht','prix achat ht','achatht','achat ht'],
            'coefficient'=>['coef','coefficient','coéficient'], 'prix_vente_ht'=>['puvht','p.u.v.ht','p u v ht','puv ht','p.u.v ht','prixventeht','prix vente ht','venteht','vente ht'],
            'taux_tva'=>['tva','tauxtva','taux tva','tva%','tva %'],
        ];
        $map = [];
        foreach ($headers as $column => $header) {
            $normalized = $this->normalizeHeader((string)$header);
            foreach ($wanted as $key => $aliases) if (in_array($normalized, array_map([$this,'normalizeHeader'],$aliases), true)) $map[$key] = $column;
        }
        return $map;
    }
    private function normalizeHeader(string $value): string { $value = mb_strtolower(trim($value)); return preg_replace('/[^a-z0-9%]+/u', '', strtr($value, ['é'=>'e','è'=>'e','ê'=>'e','à'=>'a','ù'=>'u','î'=>'i','ï'=>'i','ô'=>'o','ç'=>'c'])) ?? ''; }
    private function cell(array $row, array $map, string $key): mixed { return isset($map[$key]) ? ($row[$map[$key]] ?? null) : null; }
    private function tva(mixed $value): float { $number = $this->number(str_replace('%','',(string)$value)); return $number > 0 ? $number : 20.0; }
}
