<?php

namespace App\Services\Tureb;

use App\Models\TurebGuide;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;

/**
 * TUREB Rehber Veritabanı'nı yerel aynaya (tureb_guides) çeker.
 *
 * Nasıl çalışır
 * -------------
 * Kaynak uç: POST /RehberVeritabani/Listele  (odaId, bolgeId, yabanciDilId)
 * → sunucu, filtreye uyan TÜM satırları tek bir HTML tablo olarak basar
 *   (sayfalama istemci tarafında DataTables ile yapılır).
 *
 * "Tümü/Tümü/Tümü" reddediliyor ("Lütfen en az bir seçim yapınız"), bu yüzden
 * BÖLGE üzerinden dönülür: 8 istek tüm sicili kapsar ve satırın kendisi zaten
 * oda/dil/telefon/e-posta/durum taşır. Oda üzerinden dönmek 13 istek olurdu ve
 * bölge bilgisi satırda olmadığı için elde edilemezdi.
 *
 * Bir rehber birden fazla bölgede listelenebildiği için kayıtlar
 * source_hash ile tekilleştirilir; bölge ve diller birleştirilir.
 */
class TurebGuideSyncService
{
    private const BASE_URL = 'https://www.tureb.org.tr';
    private const LIST_PATH = '/RehberVeritabani/Listele';

    /** TUREB bölge filtresi: id => görünen ad. */
    public const REGIONS = [
        -1 => 'Ülkesel',
        6 => 'Marmara',
        7 => 'Ege',
        8 => 'Akdeniz',
        9 => 'Karadeniz',
        10 => 'İç Anadolu',
        11 => 'Güneydoğu Anadolu',
        12 => 'Doğu Anadolu',
    ];

    /** İstekler arası bekleme (saniye) — kaynak sunucuyu yormamak için. */
    private const DELAY_SECONDS = 2;

    /**
     * Tüm sicili çeker ve upsert eder.
     *
     * @param  callable|null  $onProgress  fn(string $region, int $rows)
     * @return array{fetched: int, unique: int, created: int, updated: int}
     */
    public function sync(?callable $onProgress = null): array
    {
        $guides = [];
        $fetched = 0;

        foreach (self::REGIONS as $id => $regionName) {
            $rows = $this->parseRows($this->fetchRegion($id));
            $fetched += count($rows);

            foreach ($rows as $row) {
                $this->mergeRow($guides, $row, $regionName);
            }

            if ($onProgress) {
                $onProgress($regionName, count($rows));
            }

            // Son bölgeden sonra beklemeye gerek yok.
            if ($id !== array_key_last(self::REGIONS)) {
                sleep(self::DELAY_SECONDS);
            }
        }

        $result = $this->persist($guides);

        return ['fetched' => $fetched, 'unique' => count($guides)] + $result;
    }

    /** Tek bir bölgenin HTML tablosunu getirir. */
    private function fetchRegion(int $bolgeId): string
    {
        $response = Http::asForm()
            ->withHeaders([
                'User-Agent' => 'AcenteBot/1.0 (+tur operatörü rehber sicili senkronu)',
                'X-Requested-With' => 'XMLHttpRequest',
                'Referer' => self::BASE_URL . '/RehberVeritabani',
            ])
            ->timeout(120)
            ->retry(3, 3000)
            ->post(self::BASE_URL . self::LIST_PATH, [
                'odaId' => 0,
                'bolgeId' => $bolgeId,
                'yabanciDilId' => 0,
            ]);

        $response->throw();
        $body = $response->body();

        // Uç, hata durumunda 200 ile JSON döner: {"IsOk":false,"ErrorMsg":"…"}
        if (str_starts_with(ltrim($body), '{')) {
            $json = json_decode($body, true);
            throw new \RuntimeException(
                'TUREB bölge ' . $bolgeId . ' reddedildi: ' . ($json['ErrorMsg'] ?? $body)
            );
        }

        return $body;
    }

    /**
     * HTML tablo satırlarını ayrıştırır.
     * Kolonlar: Adı Soyadı | Bağlı Olduğu Oda | Yabancı Dil | Telefon | E-Posta | Durum
     *
     * @return array<int, array{name: string, chamber: string, languages: string, phone: string, email: string, status: string}>
     */
    private function parseRows(string $html): array
    {
        if (!preg_match_all('/<tr>\s*(<td.*?)<\/tr>/s', $html, $matches)) {
            return [];
        }

        $rows = [];

        foreach ($matches[1] as $rowHtml) {
            preg_match_all('/<td[^>]*>(.*?)<\/td>/s', $rowHtml, $cells);
            $f = array_map(fn ($c) => $this->clean($c), $cells[1]);

            if (count($f) < 6 || $f[0] === '') {
                continue; // başlık/boş satır
            }

            $rows[] = [
                'name' => $f[0],
                'chamber' => $f[1],
                'languages' => $f[2],
                'phone' => $f[3],
                'email' => $f[4],
                'status' => $f[5],
            ];
        }

        return $rows;
    }

    private function clean(string $value): string
    {
        return trim(preg_replace('/\s+/u', ' ', html_entity_decode(strip_tags($value), ENT_QUOTES | ENT_HTML5, 'UTF-8')));
    }

    /** Satırı hash'ine göre birikime katar; bölge ve dilleri birleştirir. */
    private function mergeRow(array &$guides, array $row, string $regionName): void
    {
        $hash = TurebGuide::makeHash($row['name'], $row['chamber'], $row['phone'], $row['email']);

        $languages = array_values(array_filter(array_map('trim', explode(',', $row['languages']))));

        if (!isset($guides[$hash])) {
            $guides[$hash] = [
                'source_hash' => $hash,
                'full_name' => $row['name'],
                'chamber_code' => $row['chamber'] ?: null,
                'regions' => [],
                'languages' => [],
                'phone' => $row['phone'] ?: null,
                'email' => $row['email'] ?: null,
                'status' => $row['status'] ?: null,
            ];
        }

        $g = &$guides[$hash];
        $g['regions'] = array_values(array_unique([...$g['regions'], $regionName]));
        $g['languages'] = array_values(array_unique([...$g['languages'], ...$languages]));
    }

    /**
     * Toplanan kayıtları yazar. Sicil dışarıdan gelen bir aynadır; source_hash
     * üzerinden upsert edilir, TUREB'den düşen kayıtlar silinmez (acente
     * kaynakları onlara bağlı olabilir) — yalnızca fetched_at'leri eskir.
     *
     * @return array{created: int, updated: int}
     */
    private function persist(array $guides): array
    {
        $now = Carbon::now();
        $existing = TurebGuide::pluck('id', 'source_hash');
        $created = 0;
        $updated = 0;

        foreach (array_chunk($guides, 500) as $chunk) {
            $payload = [];

            foreach ($chunk as $g) {
                isset($existing[$g['source_hash']]) ? $updated++ : $created++;

                $payload[] = [
                    'source_hash' => $g['source_hash'],
                    'full_name' => $g['full_name'],
                    'chamber_code' => $g['chamber_code'],
                    'regions' => json_encode($g['regions'], JSON_UNESCAPED_UNICODE),
                    'languages' => json_encode($g['languages'], JSON_UNESCAPED_UNICODE),
                    'phone' => $g['phone'],
                    'email' => $g['email'],
                    'status' => $g['status'],
                    'fetched_at' => $now,
                    'created_at' => $now,
                    'updated_at' => $now,
                ];
            }

            DB::table('tureb_guides')->upsert(
                $payload,
                ['source_hash'],
                ['full_name', 'chamber_code', 'regions', 'languages', 'phone', 'email', 'status', 'fetched_at', 'updated_at']
            );
        }

        return ['created' => $created, 'updated' => $updated];
    }
}
