<?php
class ExcelProcessor {
    private $uploadDir;
    
    public function __construct() {
        $this->uploadDir = __DIR__ . '/../uploads/';
        
        // Upload dizinini oluştur
        if (!is_dir($this->uploadDir)) {
            mkdir($this->uploadDir, 0755, true);
        }
    }
    
    public function processFiles($bankFile, $stockFile) {
        try {
            // Uploads klasörünü temizle (eski dosyaları sil)
            $this->cleanUploadsDirectory();
            
            // Dosya yükleme kontrolleri
            $bankValidation = $this->validateFile($bankFile);
            if (!$bankValidation['valid']) {
                return ['success' => false, 'error' => 'Banka dosyası: ' . $bankValidation['error']];
            }
            
            $stockValidation = $this->validateFile($stockFile);
            if (!$stockValidation['valid']) {
                return ['success' => false, 'error' => 'Stok dosyası: ' . $stockValidation['error']];
            }
            
            // Dosyaları yükle
            $bankPath = $this->uploadFile($bankFile, 'bank_');
            $stockPath = $this->uploadFile($stockFile, 'stock_');
            
            // Excel dosyalarını oku
            $bankData = $this->readExcelFile($bankPath);
            $stockData = $this->readExcelFile($stockPath);
            
            // Verileri karşılaştır
            $comparisonResult = $this->compareData($bankData, $stockData);
            
            // Verileri JSON formatına çevir
            $bankJson = $this->convertToJson($bankData, 'bank');
            $stockJson = $this->convertToJson($stockData, 'stock');
            
            // JSON dosyalarını kaydet
            $this->saveJsonFiles($bankJson, $stockJson);
            
            // İşlem bittikten sonra uploads klasörünü temizle
            $this->cleanUploadsDirectory();
            
            return [
                'success' => true,
                'bank_count' => count($bankData) - 1, // Başlık hariç
                'stock_count' => count($stockData) - 1, // Başlık hariç
                'matches' => $comparisonResult['matches'],
                'unmatched_bank' => $comparisonResult['unmatched_bank'],
                'message' => 'Veriler karşılaştırıldı ve JSON formatına çevrildi.'
            ];
            
        } catch (Exception $e) {
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }
    
    private function validateFile($file) {
        if ($file['error'] !== UPLOAD_ERR_OK) {
            return ['valid' => false, 'error' => 'Dosya yükleme hatası.'];
        }
        
        if ($file['size'] > 10 * 1024 * 1024) { // 10MB
            return ['valid' => false, 'error' => 'Dosya boyutu çok büyük. Maksimum 10MB olmalı.'];
        }
        
        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if (!in_array($extension, ['xlsx', 'xls'])) {
            return ['valid' => false, 'error' => 'Desteklenmeyen dosya formatı. Sadece .xlsx ve .xls dosyaları kabul edilir.'];
        }
        
        return ['valid' => true];
    }
    
    private function uploadFile($file, $prefix) {
        $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
        $filename = $prefix . uniqid() . '.' . $extension;
        $filepath = $this->uploadDir . $filename;
        
        if (!move_uploaded_file($file['tmp_name'], $filepath)) {
            throw new Exception('Dosya yükleme başarısız.');
        }
        
        return $filepath;
    }
    
    private function readExcelFile($filepath) {
        // Basit Excel okuma (PhpSpreadsheet olmadan)
        $data = [];
        
        // Dosya uzantısına göre okuma yöntemi seç
        $extension = strtolower(pathinfo($filepath, PATHINFO_EXTENSION));
        
        if ($extension === 'csv') {
            // CSV dosyası
            if (($handle = fopen($filepath, "r")) !== FALSE) {
                while (($row = fgetcsv($handle, 1000, ",")) !== FALSE) {
                    $data[] = $row;
                }
                fclose($handle);
            }
        } else {
            // Excel dosyası için basit okuma
            // ZIP olarak aç ve XML'den oku
            $zip = new ZipArchive();
            if ($zip->open($filepath) === TRUE) {
                // sharedStrings.xml dosyasını oku
                $sharedStrings = '';
                if ($zip->locateName('xl/sharedStrings.xml') !== false) {
                    $sharedStrings = $zip->getFromName('xl/sharedStrings.xml');
                }
                
                // worksheet1.xml dosyasını oku
                $worksheet = '';
                if ($zip->locateName('xl/worksheets/sheet1.xml') !== false) {
                    $worksheet = $zip->getFromName('xl/worksheets/sheet1.xml');
                }
                
                $zip->close();
                
                // XML'den veri çıkar
                $data = $this->parseExcelXML($worksheet, $sharedStrings);
            } else {
                throw new Exception('Excel dosyası açılamadı');
            }
        }
        
        return $data;
    }
    
    private function parseExcelXML($worksheet, $sharedStrings) {
        $data = [];
        
        // Basit XML parsing
        $worksheetDoc = new DOMDocument();
        $worksheetDoc->loadXML($worksheet);
        
        $sharedStringsDoc = new DOMDocument();
        if (!empty($sharedStrings)) {
            $sharedStringsDoc->loadXML($sharedStrings);
        }
        
        $rows = $worksheetDoc->getElementsByTagName('row');
        foreach ($rows as $row) {
            $rowData = [];
            $cells = $row->getElementsByTagName('c');
            
            foreach ($cells as $cell) {
                $value = '';
                $v = $cell->getElementsByTagName('v');
                if ($v->length > 0) {
                    $cellValue = $v->item(0)->nodeValue;
                    
                    // Shared string kontrolü
                    $t = $cell->getAttribute('t');
                    if ($t === 's' && !empty($sharedStrings)) {
                        // Shared string'den değeri al
                        $si = $sharedStringsDoc->getElementsByTagName('si');
                        if ($si->length > $cellValue) {
                            $text = $si->item($cellValue)->getElementsByTagName('t');
                            if ($text->length > 0) {
                                $value = $text->item(0)->nodeValue;
                            }
                        }
                    } else {
                        $value = $cellValue;
                    }
                }
                $rowData[] = $value;
            }
            $data[] = $rowData;
        }
        
        return $data;
    }
    
    
    private function compareData($bankData, $stockData) {
        $matches = [];
        $unmatchedBankRecords = [];
        $unmatchedStockRecords = [];
        
        // İlk satır başlık olarak kabul edilir
        $bankHeaders = $bankData[0] ?? [];
        $stockHeaders = $stockData[0] ?? [];
        
        // Veri satırları (başlık hariç)
        $bankRows = array_slice($bankData, 1);
        $stockRows = array_slice($stockData, 1);
        
        // Sütun indekslerini belirle (0-based index)
        $bankAccountCol = 4; // E sütunu (5-1=4)
        $bankAmountCol = 10; // K sütunu (11-1=10)
        $stockAccountCol = 14; // O sütunu (15-1=14)
        $stockAmountCol = 9; // J sütunu (10-1=9)
        
        // HIZLI ALGORİTMA: ML verilerini tutar bazında grupla
        $stockByAmount = [];
        $usedStockIndices = []; // Kullanılan ML kayıtlarını takip et
        foreach ($stockRows as $stockIndex => $stockRow) {
            $stockAmount = $this->parseAmount($stockRow[$stockAmountCol] ?? 0);
            $stockAccount = trim($stockRow[$stockAccountCol] ?? '');
            
            if (!empty($stockAccount)) {
                $amountKey = (string) round($stockAmount, 2); // Virgülden sonra 2 hane
                if (!isset($stockByAmount[$amountKey])) {
                    $stockByAmount[$amountKey] = [];
                }
                $stockByAmount[$amountKey][] = [
                    'index' => $stockIndex,
                    'account' => $stockAccount,
                    'amount' => $stockAmount,
                    'row' => $stockRow
                ];
            } else {
                // Boş hesap adı olan ML kayıtlarını eşleşmeyen olarak ekle
                $unmatchedStockRecords[] = [
                    'stock_row' => (int) $stockIndex + 2,
                    'stock_account' => '(Boş Hesap Adı)',
                    'amount' => $stockAmount,
                    'stock_data' => $stockRow,
                    'stock_headers' => $stockHeaders,
                    'filter_reason' => 'Boş hesap adı'
                ];
            }
        }
        
        // Banka verilerini kontrol et
        $filteredCount = 0;
        foreach ($bankRows as $bankIndex => $bankRow) {
            $bankAmount = $this->parseAmount($bankRow[$bankAmountCol] ?? 0);
            $bankAccount = trim($bankRow[$bankAccountCol] ?? '');
            
            if (empty($bankAccount)) {
                $filteredCount++;
                // Boş hesap adı olan kayıtları da eşleşmeyen olarak ekle
                $unmatchedBankRecords[] = [
                    'bank_row' => (int) $bankIndex + 2,
                    'bank_account' => '(Boş Hesap Adı)',
                    'amount' => $bankAmount,
                    'bank_data' => $bankRow,
                    'bank_headers' => $bankHeaders,
                    'filter_reason' => 'Boş hesap adı'
                ];
                continue;
            }
            
            $amountKey = (string) round($bankAmount, 2);
            $isMatched = false;
            
            // Sadece aynı tutardaki ML kayıtları kontrol et
            if (isset($stockByAmount[$amountKey])) {
                foreach ($stockByAmount[$amountKey] as $stockItem) {
                    $similarity = $this->calculateSimilarity($bankAccount, $stockItem['account']);
                    
                    if ($similarity > 0.4) { // %40'dan fazla benzerlik
                        $matches[] = [
                            'bank_row' => (int) $bankIndex + 2,
                            'stock_row' => (int) $stockItem['index'] + 2,
                            'bank_account' => $bankAccount,
                            'stock_account' => $stockItem['account'],
                            'amount' => $bankAmount,
                            'similarity' => round($similarity * 100, 1),
                            'bank_data' => $bankRow,
                            'stock_data' => $stockItem['row'],
                            'bank_headers' => $bankHeaders,
                            'stock_headers' => $stockHeaders
                        ];
                        $isMatched = true;
                        $usedStockIndices[] = (int) $stockItem['index']; // ML kaydını kullanıldı olarak işaretle
                    }
                }
            }
            
            // Eşleşmeyen banka kayıtlarını kaydet
            if (!$isMatched) {
                $unmatchedBankRecords[] = [
                    'bank_row' => (int) $bankIndex + 2,
                    'bank_account' => $bankAccount,
                    'amount' => $bankAmount,
                    'bank_data' => $bankRow,
                    'bank_headers' => $bankHeaders
                ];
            }
        }
        
        // Kullanılmayan ML kayıtlarını eşleşmeyen olarak ekle
        foreach ($stockRows as $stockIndex => $stockRow) {
            $stockAmount = $this->parseAmount($stockRow[$stockAmountCol] ?? 0);
            $stockAccount = trim($stockRow[$stockAccountCol] ?? '');
            
            // Sadece geçerli kayıtları kontrol et ve kullanılmamış olanları ekle
            if (!empty($stockAccount) && !in_array((int) $stockIndex, $usedStockIndices)) {
                $unmatchedStockRecords[] = [
                    'stock_row' => (int) $stockIndex + 2,
                    'stock_account' => $stockAccount,
                    'amount' => $stockAmount,
                    'stock_data' => $stockRow,
                    'stock_headers' => $stockHeaders,
                    'filter_reason' => 'Banka\'da bulunamadı'
                ];
            }
        }
        
        return [
            'matches' => $matches,
            'unmatched_bank' => $unmatchedBankRecords,
            'unmatched_stock' => $unmatchedStockRecords,
            'debug_info' => [
                'total_bank_rows' => count($bankRows),
                'total_stock_rows' => count($stockRows),
                'filtered_bank_count' => $filteredCount,
                'matched_count' => count($matches),
                'unmatched_bank_count' => count($unmatchedBankRecords),
                'unmatched_stock_count' => count($unmatchedStockRecords)
            ]
        ];
    }
    
    private function parseAmount($value) {
        // Null veya boş değer kontrolü
        if ($value === null || $value === '') {
            return 0;
        }
        
        // Zaten sayı ise
        if (is_numeric($value)) {
            return (float) $value;
        }
        
        // String ise sayısal kısmını çıkar
        if (is_string($value)) {
            $value = preg_replace('/[^0-9.,\-]/', '', $value);
            $value = str_replace(',', '.', $value);
            
            return is_numeric($value) ? (float) $value : 0;
        }
        
        return 0;
    }
    
    private function calculateSimilarity($str1, $str2) {
        // Türkçe karakterleri normalize et
        $str1 = $this->normalizeText($str1);
        $str2 = $this->normalizeText($str2);
        
        if ($str1 === $str2) return 1.0;
        
        // HIZLI ALGORİTMA: Sadece en etkili yöntemleri kullan
        // PHP built-in similar_text (en hızlı ve etkili)
        similar_text($str1, $str2, $similarTextPercent);
        $similarTextScore = $similarTextPercent / 100;
        
        // Levenshtein distance (hızlı)
        $maxLen = max(strlen($str1), strlen($str2));
        if ($maxLen == 0) return 0;
        
        $distance = levenshtein($str1, $str2);
        $levenshteinScore = 1 - ($distance / $maxLen);
        
        // En yüksek skoru al
        $bestScore = max($similarTextScore, $levenshteinScore);
        
        return $bestScore;
    }
    
    // Jaro-Winkler benzerlik algoritması
    private function jaroWinkler($s1, $s2) {
        $s1_len = strlen($s1);
        $s2_len = strlen($s2);
        
        if ($s1_len == 0) return $s2_len == 0 ? 1.0 : 0.0;
        if ($s2_len == 0) return 0.0;
        
        $match_window = max($s1_len, $s2_len) / 2 - 1;
        $s1_matches = array_fill(0, $s1_len, false);
        $s2_matches = array_fill(0, $s2_len, false);
        
        $matches = 0;
        $transpositions = 0;
        
        // Eşleşen karakterleri bul
        for ($i = 0; $i < $s1_len; $i++) {
            $start = max(0, $i - $match_window);
            $end = min($i + $match_window + 1, $s2_len);
            
            for ($j = $start; $j < $end; $j++) {
                if ($s2_matches[$j] || $s1[$i] != $s2[$j]) continue;
                $s1_matches[$i] = true;
                $s2_matches[$j] = true;
                $matches++;
                break;
            }
        }
        
        if ($matches == 0) return 0.0;
        
        // Transpozisyonları say
        $k = 0;
        for ($i = 0; $i < $s1_len; $i++) {
            if (!$s1_matches[$i]) continue;
            while (!$s2_matches[$k]) $k++;
            if ($s1[$i] != $s2[$k]) $transpositions++;
            $k++;
        }
        
        $jaro = ($matches / $s1_len + $matches / $s2_len + ($matches - $transpositions / 2) / $matches) / 3;
        
        // Winkler bonusu (ilk 4 karakter için)
        $prefix = 0;
        for ($i = 0; $i < min(4, min($s1_len, $s2_len)); $i++) {
            if ($s1[$i] == $s2[$i]) $prefix++;
            else break;
        }
        
        return $jaro + (0.1 * $prefix * (1 - $jaro));
    }
    
    // Gelişmiş kelime bazlı eşleşme skoru
    private function calculateAdvancedWordMatch($str1, $str2) {
        $words1 = $this->extractWords($str1);
        $words2 = $this->extractWords($str2);
        
        if (empty($words1) || empty($words2)) return 0;
        
        $totalMatches = 0;
        $totalWeight = 0;
        
        foreach ($words1 as $word1) {
            $bestMatch = 0;
            $wordWeight = $this->getWordWeight($word1);
            
            foreach ($words2 as $word2) {
                $similarity = $this->calculateWordSimilarity($word1, $word2);
                if ($similarity > $bestMatch) {
                    $bestMatch = $similarity;
                }
            }
            
            $totalMatches += $bestMatch * $wordWeight;
            $totalWeight += $wordWeight;
        }
        
        return $totalWeight > 0 ? $totalMatches / $totalWeight : 0;
    }
    
    // Kelimeleri çıkar ve temizle
    private function extractWords($text) {
        // Noktalama işaretlerini kaldır ve kelimelere böl
        $text = preg_replace('/[^\w\s]/u', ' ', $text);
        $words = preg_split('/\s+/', trim($text));
        
        // Boş kelimeleri ve çok kısa kelimeleri filtrele
        $words = array_filter($words, function($word) {
            return strlen($word) >= 2;
        });
        
        return array_values($words);
    }
    
    // Kelime ağırlığı (uzun kelimeler daha önemli)
    private function getWordWeight($word) {
        $length = strlen($word);
        if ($length <= 2) return 0.5;
        if ($length <= 4) return 1.0;
        if ($length <= 6) return 1.5;
        return 2.0;
    }
    
    // İki kelime arasındaki benzerlik
    private function calculateWordSimilarity($word1, $word2) {
        if ($word1 === $word2) return 1.0;
        
        $len1 = strlen($word1);
        $len2 = strlen($word2);
        
        if ($len1 == 0 || $len2 == 0) return 0;
        
        // Çok kısa kelimeler için tam eşleşme gerekli
        if ($len1 <= 2 || $len2 <= 2) {
            return $word1 === $word2 ? 1.0 : 0;
        }
        
        // Levenshtein distance
        $maxLen = max($len1, $len2);
        $distance = levenshtein($word1, $word2);
        $levenshteinScore = 1 - ($distance / $maxLen);
        
        // Jaro-Winkler
        $jaroScore = $this->jaroWinkler($word1, $word2);
        
        // Alt string eşleşmesi (bir kelime diğerinin içinde geçiyor mu?)
        $substringScore = 0;
        if (strpos($word1, $word2) !== false || strpos($word2, $word1) !== false) {
            $substringScore = 0.8;
        }
        
        return max($levenshteinScore, $jaroScore, $substringScore);
    }
    
    // Anahtar kelime eşleşmesi (bonus puan)
    private function calculateKeywordMatch($str1, $str2) {
        // Önemli kelimeler (şirket, ortaklık, vs.)
        $importantWords = ['ortaklik', 'ortakligi', 'sirket', 'sirketi', 'ltd', 'ltdsti', 'as', 'as', 've', 've', 've', 've'];
        
        $words1 = $this->extractWords($str1);
        $words2 = $this->extractWords($str2);
        
        $bonus = 0;
        
        foreach ($importantWords as $keyword) {
            $found1 = false;
            $found2 = false;
            
            foreach ($words1 as $word) {
                if (strpos($word, $keyword) !== false) {
                    $found1 = true;
                    break;
                }
            }
            
            foreach ($words2 as $word) {
                if (strpos($word, $keyword) !== false) {
                    $found2 = true;
                    break;
                }
            }
            
            if ($found1 && $found2) {
                $bonus += 0.1; // Her eşleşen anahtar kelime için %10 bonus
            }
        }
        
        return min(0.3, $bonus); // Maksimum %30 bonus
    }
    
    // Dice Coefficient (n-gram benzerliği)
    private function calculateDiceCoefficient($str1, $str2) {
        $str1 = strtolower($str1);
        $str2 = strtolower($str2);
        
        // 2-gram'ları oluştur
        $bigrams1 = $this->getBigrams($str1);
        $bigrams2 = $this->getBigrams($str2);
        
        if (empty($bigrams1) || empty($bigrams2)) return 0;
        
        // Ortak bigram'ları bul
        $intersection = array_intersect($bigrams1, $bigrams2);
        $intersectionCount = count($intersection);
        
        // Dice Coefficient = 2 * |A ∩ B| / (|A| + |B|)
        $dice = (2 * $intersectionCount) / (count($bigrams1) + count($bigrams2));
        
        return $dice;
    }
    
    // 2-gram'ları çıkar
    private function getBigrams($str) {
        $bigrams = [];
        $len = strlen($str);
        
        for ($i = 0; $i < $len - 1; $i++) {
            $bigram = substr($str, $i, 2);
            // Sadece harf içeren bigram'ları al
            if (preg_match('/[a-z]{2}/', $bigram)) {
                $bigrams[] = $bigram;
            }
        }
        
        return $bigrams;
    }
    
    // Excel verilerini JSON formatına çevir
    private function convertToJson($data, $type) {
        $headers = $data[0] ?? [];
        $rows = array_slice($data, 1);
        
        // Sütun indekslerini belirle
        if ($type === 'bank') {
            $accountCol = 4; // E sütunu (5-1=4)
            $amountCol = 10; // K sütunu (11-1=10)
        } else {
            $accountCol = 14; // O sütunu (15-1=14)
            $amountCol = 9; // J sütunu (10-1=9)
        }
        
        $jsonData = [];
        foreach ($rows as $index => $row) {
            $account = trim($row[$accountCol] ?? '');
            $amount = $this->parseAmount($row[$amountCol] ?? 0);
            
            // Tüm satırları dahil et (filtreleme kaldırıldı)
            $jsonData[] = [
                'row' => $index + 1, // Excel satır numarası (1'den başlar)
                'account' => $account,
                'amount' => $amount,
                'data' => $row,
                'headers' => $headers
            ];
        }
        
        return $jsonData;
    }
    
    // JSON dosyalarını kaydet
    private function saveJsonFiles($bankJson, $stockJson) {
        $jsonDir = __DIR__ . '/../assets/json/';
        if (!is_dir($jsonDir)) {
            mkdir($jsonDir, 0755, true);
        }
        
        // JSON dosyalarını kaydet
        file_put_contents($jsonDir . 'bank_data.json', json_encode($bankJson, JSON_UNESCAPED_UNICODE));
        file_put_contents($jsonDir . 'stock_data.json', json_encode($stockJson, JSON_UNESCAPED_UNICODE));
    }
    
    // Uploads klasörünü temizle
    private function cleanUploadsDirectory() {
        if (!is_dir($this->uploadDir)) {
            return;
        }
        
        $files = glob($this->uploadDir . '*');
        foreach ($files as $file) {
            if (is_file($file)) {
                unlink($file);
            }
        }
    }
    
    private function normalizeText($text) {
        // Null veya boş değer kontrolü
        if ($text === null || $text === '') {
            return '';
        }
        
        $text = trim($text);
        if (empty($text)) return '';
        
        // String kontrolü
        if (!is_string($text)) {
            $text = (string) $text;
        }
        
        // Türkçe karakterleri dönüştür
        $turkishChars = [
            'ç' => 'c', 'Ç' => 'C',
            'ğ' => 'g', 'Ğ' => 'G',
            'ı' => 'i', 'I' => 'I',
            'ö' => 'o', 'Ö' => 'O',
            'ş' => 's', 'Ş' => 'S',
            'ü' => 'u', 'Ü' => 'U'
        ];
        
        $text = strtr($text, $turkishChars);
        $text = mb_strtolower($text, 'UTF-8');
        $text = preg_replace('/\s+/', ' ', $text);
        $text = preg_replace('/[^a-z0-9\s]/', '', $text);
        
        return trim($text);
    }
}
