<?php
declare(strict_types=1);

/**
 * Dekont kontrolü — OCR metni + kurallar (Gemini yok).
 * Öncelik: 1) tarayıcı Tesseract.js  2) sunucu Tesseract  3) defer
 *
 * @return array{verdict:string, message:string, verified:bool, method?:string, text?:string}
 */
function receipt_validate_image(string $absPath, array $order = [], string $clientOcrText = ''): array
{
    if ($absPath === '' || !is_file($absPath)) {
        return [
            'verdict' => 'reject',
            'verified' => false,
            'message' => 'Dekont dosyası okunamadı. Tekrar yükle.',
        ];
    }

    $mime = '';
    if (class_exists('finfo')) {
        $mime = (string) (new finfo(FILEINFO_MIME_TYPE))->file($absPath);
    }
    $isPdf = $mime === 'application/pdf' || $mime === 'application/x-pdf'
        || str_ends_with(strtolower($absPath), '.pdf');
    $okImage = in_array($mime, ['image/jpeg', 'image/png', 'image/webp', 'image/gif'], true);
    if (!$isPdf && !$okImage) {
        return [
            'verdict' => 'reject',
            'verified' => false,
            'message' => 'Dekont JPG, PNG, WEBP veya PDF olmalı.',
        ];
    }

    $method = '';
    $text = trim($clientOcrText);
    if ($text !== '' && mb_strlen($text) >= 8) {
        $method = 'browser';
    } else {
        $text = '';
        // PDF ham dosyadan kaba metin çekmeyi dene (metin tabanlı dekontlar)
        if ($isPdf) {
            $pdfText = receipt_pdf_extract_text_basic($absPath);
            if ($pdfText !== '' && mb_strlen($pdfText) >= 12) {
                $text = $pdfText;
                $method = 'pdf';
            }
        }
        // Opsiyonel: sunucuda Tesseract varsa (görsel)
        if ($text === '' && !$isPdf && tesseract_available()) {
            $ocrPath = receipt_prepare_ocr_path($absPath, $mime);
            if ($ocrPath !== '') {
                $serverText = tesseract_ocr_text($ocrPath);
                if ($ocrPath !== $absPath && is_file($ocrPath)) {
                    @unlink($ocrPath);
                }
                if (is_string($serverText) && trim($serverText) !== '') {
                    $text = trim($serverText);
                    $method = 'server';
                }
            }
        }
    }

    if ($text === '') {
        // OCR yok — müşteriyi engelleme, admin bakar
        return [
            'verdict' => 'defer',
            'verified' => false,
            'message' => '',
            'method' => 'none',
        ];
    }

    if (mb_strlen($text) < 12) {
        return [
            'verdict' => 'reject',
            'verified' => false,
            'message' => 'Görselde okunabilir metin yok. Net bir havale/EFT dekontu (ekran görüntüsü) yükle.',
            'method' => $method !== '' ? $method : 'browser',
            'text' => $text,
        ];
    }

    return receipt_verdict_from_text($text, $order, $method !== '' ? $method : 'browser');
}

/**
 * @return array{verdict:string, message:string, verified:bool, method:string, text:string}
 */
function receipt_verdict_from_text(string $text, array $order = [], string $method = 'browser'): array
{
    $snippet = mb_substr($text, 0, 500);
    $score = receipt_ocr_score($text, $order);

    // Önce dekont mu?
    if ($score < 2) {
        return [
            'verdict' => 'reject',
            'verified' => false,
            'message' => 'Yüklenen görsel banka dekontu gibi görünmüyor. Havale/EFT dekontunun ekran görüntüsünü yükle.',
            'method' => $method,
            'text' => $snippet,
        ];
    }

    // Tutar kontrolü — eksik / yanlış ödemeyi yakala
    $expected = (float) ($order['total'] ?? 0);
    if ($expected > 0) {
        $amountCheck = receipt_amount_check($text, $expected);
        if ($amountCheck !== 'ok') {
            $need = function_exists('money') ? money($expected) : ((string) (int) $expected . ' TL');
            $msg = match ($amountCheck) {
                'low' => 'Dekonttaki tutar eksik görünüyor. Gönderilmesi gereken: ' . $need . '. Doğru tutarla tekrar havale yapıp dekontu yükle.',
                'mismatch' => 'Dekonttaki tutar sipariş tutarıyla uyuşmuyor. Gönderilmesi gereken: ' . $need . '.',
                default => 'Dekontta sipariş tutarı okunamadı. Tutarın net göründüğü dekontu yükle. Olması gereken: ' . $need . '.',
            };
            return [
                'verdict' => 'reject',
                'verified' => false,
                'message' => $msg,
                'method' => $method,
                'text' => $snippet,
            ];
        }
    }

    return [
        'verdict' => 'accept',
        'verified' => true,
        'message' => '',
        'method' => $method,
        'text' => $snippet,
    ];
}

/**
 * OCR metninden tutarları çıkar.
 *
 * @return list<float>
 */
function receipt_parse_amounts(string $text): array
{
    $found = [];

    // 1.250,00 / 1250,00 (+ isteğe TL)
    if (preg_match_all('/(?<!\d)(\d{1,3}(?:\.\d{3})*,\d{2}|\d+,\d{2})(?!\d)/u', $text, $m)) {
        foreach ($m[1] as $raw) {
            $n = str_replace('.', '', (string) $raw);
            $n = str_replace(',', '.', $n);
            $v = (float) $n;
            if ($v >= 1 && $v <= 5000000) {
                $found[] = round($v, 2);
            }
        }
    }

    // 1.250 TL / 1250 TL (ondalıksız)
    if (preg_match_all('/(?<!\d)(\d{1,3}(?:\.\d{3})+|\d{2,7})\s*(?:TL|TRY)\b/ui', $text, $m2)) {
        foreach ($m2[1] as $raw) {
            $n = str_replace('.', '', (string) $raw);
            $v = (float) $n;
            if ($v >= 1 && $v <= 5000000) {
                $found[] = round($v, 2);
            }
        }
    }

    // 1250.00 US tarzı
    if (preg_match_all('/(?<!\d)(\d{2,7}\.\d{2})(?!\d)/u', $text, $m3)) {
        foreach ($m3[1] as $raw) {
            $v = (float) $raw;
            if ($v >= 1 && $v <= 5000000) {
                $found[] = round($v, 2);
            }
        }
    }

    return array_values(array_unique($found));
}

/**
 * @return 'ok'|'missing'|'low'|'mismatch'
 */
function receipt_amount_check(string $text, float $expected): string
{
    $expected = round($expected, 2);
    if ($expected <= 0) {
        return 'ok';
    }

    $norm = mb_strtoupper($text);
    $compact = preg_replace('/\s+/u', '', $norm) ?? $norm;

    // Beklenen tutarın yaygın yazımları metinde geçiyor mu?
    $plain = (string) (int) round($expected);
    $withDot = number_format($expected, 0, ',', '.'); // 1.250
    $withComma = number_format($expected, 2, ',', '.'); // 1.250,00
    $withCommaNoDot = number_format($expected, 2, ',', ''); // 1250,00
    $us = number_format($expected, 2, '.', ''); // 1250.00

    $needles = array_unique(array_filter([$plain, $withDot, $withComma, $withCommaNoDot, $us]));
    foreach ($needles as $needle) {
        // "520" → "1520" içinde sayılmasın
        if (preg_match('/(?<!\d)' . preg_quote($needle, '/') . '(?!\d)/u', $norm)) {
            return 'ok';
        }
    }
    // Boşluksuz: 125000 (1250,00) gibi OCR bozulmaları için sadece tam kuruş
    $cents = (string) (int) round($expected * 100);
    if ($cents !== '' && preg_match('/(?<!\d)' . preg_quote($cents, '/') . '(?!\d)/u', $compact)) {
        // 520,00 → 52000; yanlış pozitif riski düşük tutarlar için sınırla
        if ($expected >= 10) {
            return 'ok';
        }
    }

    $amounts = receipt_parse_amounts($text);
    if (!$amounts) {
        return 'missing';
    }

    $expCents = (int) round($expected * 100);
    foreach ($amounts as $a) {
        if (abs((int) round($a * 100) - $expCents) <= 1) {
            return 'ok';
        }
    }

    $max = max($amounts);
    if ($max + 0.009 < $expected) {
        return 'low';
    }

    return 'mismatch';
}

/**
 * Basit PDF metin çıkarma (harici kütüphane yok).
 * Metin katmanlı banka PDF’lerinde işe yarar; taranmış PDF’te boş kalabilir.
 */
function receipt_pdf_extract_text_basic(string $path): string
{
    $raw = @file_get_contents($path);
    if ($raw === false || $raw === '') {
        return '';
    }
    $out = [];
    // (...) Tj  ve  [ (...) ] TJ
    if (preg_match_all('/\((?:\\\\.|[^\\\\()])*\)\s*Tj/s', $raw, $m)) {
        foreach ($m[0] as $chunk) {
            if (preg_match('/^\((.*)\)\s*Tj$/s', $chunk, $mm)) {
                $out[] = receipt_pdf_unescape($mm[1]);
            }
        }
    }
    if (preg_match_all('/\[(.*?)\]\s*TJ/s', $raw, $m2)) {
        foreach ($m2[1] as $arr) {
            if (preg_match_all('/\((?:\\\\.|[^\\\\()])*\)/', $arr, $parts)) {
                foreach ($parts[0] as $p) {
                    $out[] = receipt_pdf_unescape(substr($p, 1, -1));
                }
            }
        }
    }
    $text = trim(implode(' ', $out));
    $text = preg_replace('/[^\P{C}\n\t]+/u', ' ', $text) ?? $text;
    return trim(preg_replace('/\s+/u', ' ', $text) ?? $text);
}

function receipt_pdf_unescape(string $s): string
{
    $s = str_replace(['\\n', '\\r', '\\t', '\\(', '\\)', '\\\\'], ["\n", "\r", "\t", '(', ')', '\\'], $s);
    return $s;
}

function tesseract_available(): bool
{
    static $cached = null;
    if ($cached !== null) {
        return $cached;
    }
    $bin = tesseract_binary();
    if ($bin === '') {
        $cached = false;
        return false;
    }
    if (!function_exists('exec') || in_array('exec', array_map('trim', explode(',', (string) ini_get('disable_functions'))), true)) {
        $cached = false;
        return false;
    }
    $out = [];
    $code = 1;
    @exec(escapeshellarg($bin) . ' --version 2>&1', $out, $code);
    $cached = $code === 0 || str_contains(implode(' ', $out), 'tesseract');
    return $cached;
}

function tesseract_binary(): string
{
    $custom = trim((string) (function_exists('setting') ? setting('tesseract_path') : ''));
    if ($custom !== '' && (is_file($custom) || is_executable($custom))) {
        return $custom;
    }

    $candidates = [
        'tesseract',
        'C:\\Program Files\\Tesseract-OCR\\tesseract.exe',
        'C:\\Program Files (x86)\\Tesseract-OCR\\tesseract.exe',
        '/usr/bin/tesseract',
        '/usr/local/bin/tesseract',
    ];
    foreach ($candidates as $bin) {
        if ($bin === 'tesseract') {
            continue;
        }
        if (is_file($bin)) {
            return $bin;
        }
    }
    return 'tesseract';
}

/** @return string|null OCR metni; hata/yoksa null */
function tesseract_ocr_text(string $imagePath): ?string
{
    $bin = tesseract_binary();
    if ($bin === '' || !is_file($imagePath)) {
        return null;
    }

    $tmpBase = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'ocr_' . bin2hex(random_bytes(8));
    $outFile = $tmpBase . '.txt';

    // Türkçe + İngilizce (banka uygulamalarında karışık metin olur)
    $lang = 'tur+eng';
    $cmd = sprintf(
        '%s %s %s -l %s --psm 6 2>&1',
        escapeshellarg($bin),
        escapeshellarg($imagePath),
        escapeshellarg($tmpBase),
        escapeshellarg($lang)
    );

    $out = [];
    $code = 1;
    @exec($cmd, $out, $code);

    // tur yoksa eng dene
    if (($code !== 0 || !is_file($outFile)) && is_file($imagePath)) {
        @unlink($outFile);
        $cmd = sprintf(
            '%s %s %s -l eng --psm 6 2>&1',
            escapeshellarg($bin),
            escapeshellarg($imagePath),
            escapeshellarg($tmpBase)
        );
        $out = [];
        $code = 1;
        @exec($cmd, $out, $code);
    }

    if (!is_file($outFile)) {
        return null;
    }
    $text = (string) file_get_contents($outFile);
    @unlink($outFile);
    return $text;
}

/** WebP/GIF → geçici JPEG yolu; JPEG/PNG aynı dosya. */
function receipt_prepare_ocr_path(string $absPath, string $mime): string
{
    if (in_array($mime, ['image/jpeg', 'image/png'], true)) {
        return $absPath;
    }
    if (!function_exists('imagecreatetruecolor')) {
        return '';
    }
    $src = match ($mime) {
        'image/webp' => function_exists('imagecreatefromwebp') ? @imagecreatefromwebp($absPath) : false,
        'image/gif' => @imagecreatefromgif($absPath),
        default => false,
    };
    if ($src === false) {
        return '';
    }
    $tmp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'ocrimg_' . bin2hex(random_bytes(6)) . '.jpg';
    $ok = @imagejpeg($src, $tmp, 90);
    imagedestroy($src);
    return ($ok && is_file($tmp)) ? $tmp : '';
}

/**
 * OCR metninden dekont skoru (0+).
 * 2 ve üzeri = accept.
 */
function receipt_ocr_score(string $text, array $order = []): int
{
    $norm = mb_strtoupper($text);
    $norm = strtr($norm, [
        'İ' => 'I', 'İ' => 'I', 'Ş' => 'S', 'Ğ' => 'G', 'Ü' => 'U', 'Ö' => 'O', 'Ç' => 'C',
    ]);
    // Boşlukları sadeleştir; IBAN için de boşluksuz kopya
    $compact = preg_replace('/\s+/u', '', $norm) ?? $norm;
    $score = 0;

    // TR IBAN (26 karakter: TR + 24 rakam) — boşluklu/boşluksuz
    if (preg_match('/TR\s*\d[\d\s]{20,30}\d/u', $norm) || preg_match('/TR\d{24}/', $compact)) {
        $score += 2;
    }

    $keywords = [
        'HAVALE' => 1,
        'EFT' => 1,
        'FAST' => 1,
        'GONDERILDI' => 1,
        'GONDERME' => 1,
        'TRANSFER' => 1,
        'ALICI' => 1,
        'GONDEREN' => 1,
        'ISLEM' => 1,
        'REFERANS' => 1,
        'DEKONT' => 1,
        'BAKIYE' => 1,
        'HESAP' => 1,
        'IBAN' => 1,
        'TL' => 1,
        'TRY' => 1,
        'TUTAR' => 1,
        'BANK' => 1,
        'ZIRAAT' => 1,
        'GARANTI' => 1,
        'ISBANK' => 1,
        'YAPIKREDI' => 1,
        'YAPI KREDI' => 1,
        'AKBANK' => 1,
        'QNB' => 1,
        'FINANSBANK' => 1,
        'HALKBANK' => 1,
        'VAKIF' => 1,
        'DENIZBANK' => 1,
        'TEB' => 1,
        'ING' => 1,
        'PAPARA' => 1,
        'TOSLA' => 1,
    ];
    foreach ($keywords as $kw => $pts) {
        if (str_contains($norm, $kw) || str_contains($compact, str_replace(' ', '', $kw))) {
            $score += $pts;
        }
    }

    // Tutar benzeri: 1.250,00 / 1250 TL
    if (preg_match('/\d{1,3}([.\s]\d{3})*(,\d{2})?\s*(TL|TRY)?/u', $norm)) {
        $score += 1;
    }

    // Sipariş tutarı metinde geçiyorsa ekstra
    if (isset($order['total'])) {
        $total = (float) $order['total'];
        if ($total > 0) {
            $plain = (string) (int) round($total);
            $withDot = number_format($total, 0, ',', '.');
            $withComma = number_format($total, 2, ',', '.');
            if (str_contains($compact, $plain) || str_contains($norm, $withDot) || str_contains($norm, $withComma)) {
                $score += 1;
            }
        }
    }

    // Siparişteki IBAN son 8 hane
    $iban = preg_replace('/\s+/', '', (string) ($order['iban'] ?? '')) ?? '';
    if (strlen($iban) >= 8) {
        $tail = substr($iban, -8);
        if ($tail !== '' && str_contains($compact, $tail)) {
            $score += 2;
        }
    }

    return $score;
}
