<?php

declare(strict_types=1);

namespace App\Services;

use App\Repositories\ClientRepository;
use App\Repositories\TypeClientRepository;
use RuntimeException;

final class ClientImportService
{
    private const HEADER_MAP = [
        'cdclient' => 'code_client_source',
        'codeclient' => 'code_client_source',
        'code_client' => 'code_client_source',
        'societe' => 'raison_sociale',
        'société' => 'raison_sociale',
        'raison sociale' => 'raison_sociale',
        'raison_sociale' => 'raison_sociale',
        'nom' => 'contact',
        'contact' => 'contact',
        'adresse' => 'adresse',
        'code postal' => 'code_postal',
        'codepostal' => 'code_postal',
        'code_postal' => 'code_postal',
        'ville' => 'ville',
        'telephone' => 'telephone',
        'téléphone' => 'telephone',
        'mobile' => 'mobile',
        'e-mail' => 'email',
        'email' => 'email',
        'mail' => 'email',
    ];

    public function preview(string $uploadedFile): array
    {
        $rows = (new XlsxReader())->read($uploadedFile);
        if (count($rows) < 2) {
            throw new RuntimeException('Le fichier Excel ne contient pas de données à importer.');
        }

        $header = array_shift($rows);
        $mapping = $this->buildMapping($header);
        $clients = [];
        $errors = [];
        $lineNumber = 1;

        foreach ($rows as $row) {
            $lineNumber++;
            $client = $this->rowToClient($row, $mapping);

            if ($this->isEmptyLine($client)) {
                continue;
            }

            $lineErrors = $this->validate($client, $lineNumber);
            if (!empty($lineErrors)) {
                $errors = array_merge($errors, $lineErrors);
                continue;
            }

            $clients[] = $client;
        }

        return [
            'total' => count($rows),
            'valid' => count($clients),
            'errors' => $errors,
            'clients' => $clients,
        ];
    }

    public function savePreview(array $preview): string
    {
        $dir = STORAGE_PATH . '/imports';
        if (!is_dir($dir)) {
            mkdir($dir, 0775, true);
        }

        $token = bin2hex(random_bytes(16));
        file_put_contents($dir . '/clients_' . $token . '.json', json_encode($preview, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
        return $token;
    }

    public function loadPreview(string $token): array
    {
        $file = STORAGE_PATH . '/imports/clients_' . basename($token) . '.json';
        if (!is_file($file)) {
            throw new RuntimeException('La prévisualisation de l\'import est introuvable. Recommencez l\'import.');
        }

        $data = json_decode((string) file_get_contents($file), true);
        if (!is_array($data)) {
            throw new RuntimeException('La prévisualisation de l\'import est illisible.');
        }

        return $data;
    }

    public function import(string $token, string $mode): array
    {
        $preview = $this->loadPreview($token);
        $clients = $preview['clients'] ?? [];
        $clientModel = new ClientRepository();
        $typeIds = $this->typeIds();

        $result = [
            'created' => 0,
            'updated' => 0,
            'ignored' => 0,
            'errors' => [],
        ];

        foreach ($clients as $data) {
            $data['type_client_id'] = $this->guessTypeId($data['raison_sociale'] ?? '', $typeIds);
            $existing = $clientModel->findByCode((string) $data['code_client']);

            try {
                if ($existing) {
                    if ($mode === 'update') {
                        $clientModel->update((int) $existing['id'], $data);
                        $result['updated']++;
                    } else {
                        $result['ignored']++;
                    }
                } else {
                    $clientModel->create($data);
                    $result['created']++;
                }
            } catch (\Throwable $e) {
                $result['errors'][] = 'Client ' . ($data['code_client'] ?? '-') . ' : ' . $e->getMessage();
            }
        }

        return $result;
    }

    private function buildMapping(array $header): array
    {
        $mapping = [];
        foreach ($header as $index => $label) {
            $key = $this->normalizeHeader((string) $label);
            if (isset(self::HEADER_MAP[$key])) {
                $mapping[$index] = self::HEADER_MAP[$key];
            }
        }

        if (!in_array('raison_sociale', $mapping, true)) {
            throw new RuntimeException('La colonne Société / Raison sociale est obligatoire.');
        }

        return $mapping;
    }

    private function rowToClient(array $row, array $mapping): array
    {
        $client = [
            'code_client' => '',
            'raison_sociale' => '',
            'contact' => '',
            'adresse' => '',
            'code_postal' => '',
            'ville' => '',
            'pays' => 'Maroc',
            'telephone' => '',
            'mobile' => '',
            'email' => '',
            'site_web' => '',
            'ice' => '',
            'identifiant_fiscal' => '',
            'registre_commerce' => '',
            'commercial' => '',
            'remise' => '0.00',
            'mode_reglement' => '',
            'delai_paiement' => '0',
            'plafond_credit' => '0.00',
            'observations' => 'Import Excel',
        ];

        foreach ($mapping as $index => $field) {
            $value = trim((string) ($row[$index] ?? ''));
            if ($field === 'code_client_source') {
                $client['code_client'] = $this->formatCodeClient($value);
            } else {
                $client[$field] = $this->cleanValue($field, $value);
            }
        }

        if ($client['code_client'] === '') {
            $client['code_client'] = $this->formatCodeClient((string) crc32(($client['raison_sociale'] ?? '') . ($client['ville'] ?? '')));
        }

        return $client;
    }

    private function validate(array $client, int $lineNumber): array
    {
        $errors = [];
        if (trim((string) ($client['raison_sociale'] ?? '')) === '') {
            $errors[] = 'Ligne ' . $lineNumber . ' : raison sociale manquante.';
        }

        if (($client['email'] ?? '') !== '' && !filter_var($client['email'], FILTER_VALIDATE_EMAIL)) {
            $errors[] = 'Ligne ' . $lineNumber . ' : email invalide (' . $client['email'] . ').';
        }

        return $errors;
    }

    private function isEmptyLine(array $client): bool
    {
        return trim((string) ($client['raison_sociale'] ?? '')) === ''
            && trim((string) ($client['contact'] ?? '')) === ''
            && trim((string) ($client['telephone'] ?? '')) === '';
    }

    private function cleanValue(string $field, string $value): string
    {
        $value = preg_replace('/\s+/', ' ', trim($value)) ?? '';

        if ($field === 'email') {
            return strtolower($value);
        }

        if ($field === 'ville') {
            return $this->titleCase($value);
        }

        if (in_array($field, ['telephone', 'mobile'], true)) {
            return preg_replace('/\s+/', ' ', $value) ?? $value;
        }

        return $value;
    }

    private function titleCase(string $value): string
    {
        if ($value === '') {
            return '';
        }
        return mb_convert_case(mb_strtolower($value, 'UTF-8'), MB_CASE_TITLE, 'UTF-8');
    }

    private function normalizeHeader(string $value): string
    {
        $value = mb_strtolower(trim($value), 'UTF-8');
        $value = str_replace(['é', 'è', 'ê', 'ë'], 'e', $value);
        $value = str_replace(['à', 'â'], 'a', $value);
        $value = str_replace(['î', 'ï'], 'i', $value);
        $value = str_replace(['ô'], 'o', $value);
        $value = str_replace(['ù', 'û'], 'u', $value);
        $value = preg_replace('/\s+/', ' ', $value) ?? $value;
        return $value;
    }

    private function formatCodeClient(string $value): string
    {
        $value = strtoupper(trim($value));
        if ($value === '') {
            return '';
        }

        if (str_starts_with($value, 'CL')) {
            $number = preg_replace('/\D/', '', $value) ?: '0';
            return 'CL' . str_pad($number, 6, '0', STR_PAD_LEFT);
        }

        if (is_numeric($value)) {
            return 'CL' . str_pad((string) ((int) $value), 6, '0', STR_PAD_LEFT);
        }

        $safe = preg_replace('/[^A-Z0-9]/', '', $value) ?: $value;
        return substr($safe, 0, 20);
    }

    private function typeIds(): array
    {
        $types = (new TypeClientRepository())->allActive();
        $result = [];
        foreach ($types as $type) {
            $result[mb_strtolower((string) $type['libelle'], 'UTF-8')] = (int) $type['id'];
        }
        return $result;
    }

    private function guessTypeId(string $raisonSociale, array $typeIds): ?int
    {
        $name = mb_strtolower($raisonSociale, 'UTF-8');
        if ((str_contains($name, 'hotel') || str_contains($name, 'hôtel') || str_contains($name, 'riad')) && isset($typeIds['hôtel'])) {
            return $typeIds['hôtel'];
        }
        if (str_contains($name, 'restaurant') && isset($typeIds['restaurant'])) {
            return $typeIds['restaurant'];
        }
        if (str_contains($name, 'cafe') || str_contains($name, 'café')) {
            return $typeIds['café'] ?? null;
        }
        return null;
    }
}
