<?php

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use RuntimeException;
use Throwable;

class LcrhHelloWorkService
{
    public function getOffers(): array
    {
        $ttl = (int) config('lcrh.cache.list_ttl', 300);

        $offers = Cache::remember('lcrh:offers:list', $ttl, function () {
            return $this->fetchOffers();
        });

        return !empty($offers) ? $offers : $this->demoOffers();
    }

    public function getDetail(string $id): array
    {
        $id = preg_replace('/[^a-z0-9]/i', '', $id);

        if ($id === '') {
            return [
                'ok' => false,
                'description' => '',
                'error' => app()->hasDebugModeEnabled() ? 'ID offre vide.' : null,
            ];
        }

        foreach ($this->demoOffers() as $offer) {
            if ($offer['id'] === $id || $offer['id'] === 'd' . $id || ltrim($offer['id'], 'd') === $id) {
                return [
                    'ok' => true,
                    'description' => $offer['description'] ?? '',
                ];
            }
        }

        $cacheKey = 'lcrh:offers:detail:' . $id;

        if ($cached = Cache::get($cacheKey)) {
            return $cached;
        }

        try {
            $numericId = preg_replace('/\D/', '', $id);

            if ($numericId === '') {
                throw new RuntimeException('ID HelloWork invalide : ' . $id);
            }

            $url = 'https://www.hellowork.com/fr-fr/emplois/' . $numericId . '.html';

            $html = $this->httpGet($url);
            $job = $this->extractJobPosting($html);

            $description = $this->htmlToText($job['description'] ?? '');

            if ($description === '') {
                throw new RuntimeException('Description introuvable dans le JSON-LD HelloWork pour : ' . $url);
            }

            $result = [
                'ok' => true,
                'description' => $description,
            ];

            Cache::put($cacheKey, $result, (int) config('lcrh.cache.detail_ttl', 21600));

            return $result;
        } catch (Throwable $e) {
            Log::error('[LCRH] Erreur récupération détail offre HelloWork', [
                'offer_id' => $id,
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
            ]);

            return [
                'ok' => false,
                'description' => "Description momentanément indisponible. Consultez l'offre complète.",
                'error' => app()->hasDebugModeEnabled() ? $e->getMessage() : null,
            ];
        }
    }

    public function salaryBounds(string $salary): array
    {
        $clean = preg_replace('/[\s\x{00a0}\x{202f}\x{2009}]/u', '', $salary);

        if (preg_match_all('/\d{4,}/', $clean, $matches)) {
            $numbers = array_map('intval', $matches[0]);

            if (!empty($numbers)) {
                return [min($numbers), max($numbers)];
            }
        }

        return [0, 0];
    }

    public function shortSalary(string $salary): string
    {
        [$min, $max] = $this->salaryBounds($salary);

        if ($max > 0) {
            $format = static fn (int $amount): string => $amount >= 1000
                ? round($amount / 1000) . 'k€'
                : $amount . '€';

            return $min !== $max
                ? $format($min) . '–' . $format($max)
                : $format($max);
        }

        return mb_strlen($salary) > 22 ? mb_substr($salary, 0, 22) . '…' : $salary;
    }

    private function fetchOffers(): array
    {
        try {
            $companyUrl = config('lcrh.hellowork.company_url');
            $maxPages = (int) config('lcrh.hellowork.max_pages', 15);

            $firstPage = $this->httpGet($companyUrl);
            $pages = $this->totalPages($firstPage);
            $offers = $this->parseListing($firstPage);

            for ($page = 2; $page <= min($pages, $maxPages); $page++) {
                $html = $this->httpGet($companyUrl . '?p=' . $page);
                $batch = $this->parseListing($html);

                if (empty($batch)) {
                    break;
                }

                $offers = array_merge($offers, $batch);
            }

            $offers = $this->uniqueOffers($offers);
            $offers = $this->attachCoordinates($offers);

            return $offers;
        } catch (Throwable $e) {
            report($e);

            return [];
        }
    }

    private function httpGet(string $url): string
    {
        return Http::withHeaders([
                'Accept-Language' => 'fr-FR,fr;q=0.9',
                'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36',
            ])
            ->timeout(15)
            ->retry(1, 300)
            ->get($url)
            ->throw()
            ->body();
    }

    private function totalPages(string $html): int
    {
        if (preg_match('/Page\s+1\s+sur\s+(\d+)/i', $html, $matches)) {
            return (int) $matches[1];
        }

        return 1;
    }

    private function parseListing(string $html): array
    {
        $html = html_entity_decode($html, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $employer = preg_quote(config('lcrh.hellowork.employer', 'LCRH'), '#');

        preg_match_all('#href="/fr-fr/emplois/(\d+)\.html"\s+title="(.+?) - ' . $employer . '"#', $html, $links, PREG_SET_ORDER);
        preg_match_all('/aria-label="Voir offre de ([^"]+)"/', $html, $metas, PREG_SET_ORDER);

        $offers = [];
        $count = min(count($links), count($metas));

        for ($i = 0; $i < $count; $i++) {
            $meta = $metas[$i][1];
            $location = '';
            $contract = '';
            $salary = '';
            $time = '';

            if (preg_match('/ à (.+?), chez /u', $meta, $matches)) {
                $location = $this->cleanLocation($matches[1]);
            }

            if (preg_match('/pour une? ([^,]+)/u', $meta, $matches)) {
                $contract = trim($matches[1]);
            }

            if (preg_match('/salaire de (.+?)(?:, en | en |$)/u', $meta, $matches)) {
                $salary = trim($matches[1]);
            }

            if (preg_match('/, en (temps [^,]+)/u', $meta, $matches)) {
                $time = trim($matches[1]);
            }

            $offers[] = [
                'id' => $links[$i][1],
                'title' => trim($links[$i][2]),
                'location' => $location,
                'contract' => $contract,
                'salary' => $salary,
                'salary_short' => $this->shortSalary($salary),
                'salmax' => $this->salaryBounds($salary)[1],
                'time' => $time,
                'remote' => stripos($meta, 'télétravail') !== false,
                'lat' => null,
                'lng' => null,
            ];
        }

        return $offers;
    }

    private function cleanLocation(string $location): string
    {
        $location = trim($location);

        if (preg_match('/^(.+?)\s*-\s*(\d{2,3})$/', $location, $matches)) {
            return trim($matches[1]) . ' (' . $matches[2] . ')';
        }

        return $location;
    }

    private function uniqueOffers(array $offers): array
    {
        $seen = [];

        return array_values(array_filter($offers, function (array $offer) use (&$seen) {
            if (isset($seen[$offer['id']])) {
                return false;
            }

            $seen[$offer['id']] = true;

            return true;
        }));
    }

    private function attachCoordinates(array $offers): array
    {
        foreach ($offers as &$offer) {
            if (empty($offer['location'])) {
                continue;
            }

            $coordinates = $this->geocode($offer['location']);
            $offer['lat'] = $coordinates['lat'];
            $offer['lng'] = $coordinates['lng'];
        }

        unset($offer);

        return $offers;
    }

    private function geocode(string $location): array
    {
        return Cache::rememberForever('lcrh:geo:' . md5($location), function () use ($location) {
            $city = $location;
            $department = '';

            if (preg_match('/^(.+?)\s*\((\d{2,3})\)$/u', $location, $matches)) {
                $city = trim($matches[1]);
                $department = $matches[2];
            }

            try {
                $response = Http::timeout(8)->get('https://api-adresse.data.gouv.fr/search/', [
                    'q' => $city,
                    'type' => 'municipality',
                    'limit' => 6,
                ])->throw()->json();

                $features = $response['features'] ?? [];
                $pick = null;

                foreach ($features as $feature) {
                    $postcode = $feature['properties']['postcode'] ?? '';
                    $context = $feature['properties']['context'] ?? '';

                    if ($department !== '' && (str_starts_with($postcode, $department) || str_starts_with($context, $department . ','))) {
                        $pick = $feature;
                        break;
                    }
                }

                $pick ??= $features[0] ?? null;

                if ($pick) {
                    $coordinates = $pick['geometry']['coordinates'];

                    return [
                        'lat' => $coordinates[1],
                        'lng' => $coordinates[0],
                    ];
                }
            } catch (Throwable $e) {
                report($e);
            }

            return ['lat' => null, 'lng' => null];
        });
    }

    private function extractJobPosting(string $html): array
    {
        if (preg_match_all('#<script[^>]*application/ld\+json[^>]*>(.*?)</script>#is', $html, $matches)) {
            foreach ($matches[1] as $block) {
                $json = json_decode(trim($block), true);

                if (is_array($json) && (($json['@type'] ?? '') === 'JobPosting')) {
                    return $json;
                }
            }
        }

        return [];
    }

    private function htmlToText(string $html): string
    {
        $text = preg_replace('#<\s*li[^>]*>#i', "\n• ", $html);
        $text = preg_replace('#<\s*(br|/p|/h[1-6]|/div)\s*/?>#i', "\n", $text);
        $text = strip_tags($text);
        $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $text = preg_replace('/[ \t]+/', ' ', $text);
        $text = preg_replace('/\n{3,}/', "\n\n", $text);

        return trim($text);
    }

    private function demoOffers(): array
    {
        $coordinates = [
            'Dijon (21)' => [47.32, 5.04],
            'Le Creusot (71)' => [46.80, 4.42],
            'Joigny (89)' => [47.98, 3.40],
            'Mouroux (77)' => [48.82, 3.02],
            'Troyes (10)' => [48.30, 4.08],
        ];

        $offers = [
            [
                'id' => 'd1',
                'title' => 'Chef de Chantier Électricité H/F',
                'location' => 'Dijon (21)',
                'contract' => 'CDI',
                'salary' => '30 000 - 35 000 € / an',
                'time' => 'temps plein',
                'remote' => false,
                'description' => "LCRH recrute pour son client un Chef de Chantier Électricité (H/F).\n\n• Préparer et organiser les chantiers\n• Encadrer les équipes sur le terrain\n• Garantir la qualité, les délais et la sécurité\n\nProfil : formation électrotechnique, 3 ans d'expérience en conduite de chantier.",
            ],
            [
                'id' => 'd2',
                'title' => 'Technicien Contrôle Qualité H/F',
                'location' => 'Le Creusot (71)',
                'contract' => 'CDI',
                'salary' => '30 000 - 35 000 € / an',
                'time' => 'temps plein',
                'remote' => false,
                'description' => "Pour une industrie de précision, LCRH recherche un Technicien Contrôle Qualité (H/F).\n\n• Réaliser les contrôles dimensionnels\n• Rédiger les rapports de conformité\n• Participer à l'amélioration continue\n\nProfil : Bac+2 mesures physiques ou qualité.",
            ],
            [
                'id' => 'd3',
                'title' => 'Soudeur MAG - TIG H/F',
                'location' => 'Joigny (89)',
                'contract' => 'CDI',
                'salary' => '25 000 - 30 000 € / an',
                'time' => 'temps plein',
                'remote' => false,
                'description' => "LCRH recrute un Soudeur MAG-TIG (H/F) pour un acteur de la métallerie.\n\n• Souder selon plans et procédures\n• Contrôler la qualité des soudures\n\nProfil : maîtrise des procédés MAG et TIG, lecture de plans.",
            ],
        ];

        foreach ($offers as &$offer) {
            $offer['salary_short'] = $this->shortSalary($offer['salary']);
            $offer['salmax'] = $this->salaryBounds($offer['salary'])[1];
            $coord = $coordinates[$offer['location']] ?? [null, null];
            $offer['lat'] = $coord[0];
            $offer['lng'] = $coord[1];
        }

        unset($offer);

        return $offers;
    }
}
