import socket
import threading
import queue
import json
import time
import logging
from datetime import datetime
import sqlite3
from user_management import user_manager

logger = logging.getLogger(__name__)

def get_db_connection():
    conn = sqlite3.connect('data.db')
    conn.row_factory = sqlite3.Row
    return conn

# TCP Bağlantı yöneticisi
class TCPConnectionManager:
    def __init__(self):
        self.server_socket = None
        self.client_connections = {}  # Server bağlantıları (cihazlar bize bağlanıyor)
        self.tcp_client_connections = {}  # Client bağlantıları (biz cihazlara bağlanıyoruz)
        self.connected_device_ips = {}
        self.device_response_status = {}
        self.command_responses = {}  # Komut yanıtlarını saklamak için
        self.data_queue = queue.Queue()
        self.running = False
        self.server_thread = None
        self.client_threads = {}
        self.tcp_client_threads = {}  # TCP client bağlantı thread'leri
        self.rate_limit = {}
        self.lock = threading.Lock()
        self.periodic_getdeviceinfo_thread = None
        self.tcp_client_connections = {}  # TCP client bağlantıları
        self.tcp_client_threads = {}  # TCP client thread'leri
        self.pause_periodic_getdeviceinfo = False  # UUID oluşturma sırasında durdurmak için
        self.card_collection = {}  # Kart toplama için
        self.pending_formatcards = {}  # formatcards komutu bekleyen cihazlar
        self.device_info_cache = {}  # getdeviceinfo verilerini cache'lemek için
        self.log_buffers = {}  # readlog komutları için buffer
        # Kısa süreli tekrarlı event dedup için bellek içi anahtarlar
        self.recent_access_keys = {}  # key -> last_seen_epoch
        # MASTERCARD oluşturmayı geçici bastırma süresi (epoch saniye)
        self.mastercard_suppress_until = 0
        # Kayıtsız cihaz denemeleri ve karaliste
        self.unregistered_attempts = {}  # device_id -> {'count':int,'first_ts':float,'last_ts':float}
        self.blacklist = {}  # device_id -> {'until': float, 'reason': str}
        self.terminal_action_recent = {}  # (device_id, uuid_lower) -> last_action_time

    def is_blacklisted(self, device_id: str) -> bool:
        try:
            info = self.blacklist.get(str(device_id))
            if not info:
                return False
            if time.time() >= float(info.get('until', 0)):
                # Süresi dolmuş, sil
                self.blacklist.pop(str(device_id), None)
                return False
            return True
        except Exception:
            return False

    def unblacklist(self, device_id: str):
        try:
            self.blacklist.pop(str(device_id), None)
            # Ayrıca sayaçları sıfırlayalım
            self.unregistered_attempts.pop(str(device_id), None)
        except Exception:
            pass
    
    def is_ip_blacklisted(self, ip_address: str) -> bool:
        try:
            now = time.time()
            remove_keys = []
            for dev_id, info in list(self.blacklist.items()):
                until_ts = float(info.get('until', 0) or 0)
                if until_ts and now >= until_ts:
                    remove_keys.append(dev_id)
                    continue
                if info.get('ip') == ip_address and until_ts > now:
                    return True
            for k in remove_keys:
                self.blacklist.pop(k, None)
            return False
        except Exception:
            return False
    
    def _iter_json_stream(self, text: str):
        """Metin içinde ardışık yapışık JSON objelerini sırayla yield eder."""
        try:
            decoder = json.JSONDecoder()
            idx = 0
            n = len(text)
            while idx < n:
                # Boşlukları geç
                while idx < n and text[idx].isspace():
                    idx += 1
                if idx >= n:
                    break
                # JSON başlangıcı değilse bir sonraki '{' veya '[' konumuna atla
                if text[idx] not in '{[':
                    next_obj = [p for p in (text.find('{', idx), text.find('[', idx)) if p != -1]
                    if not next_obj:
                        break
                    idx = min(next_obj)
                try:
                    obj, end = decoder.raw_decode(text, idx)
                    yield obj
                    idx = end
                except ValueError:
                    # Daha fazla geçerli JSON yok
                    break
        except Exception:
            return
    
    def _is_duplicate_access(self, device_id: str, access_type: int, card_uuid: str, timestamp_str: str) -> bool:
        try:
            key = f"{device_id}|{access_type}|{card_uuid}|{timestamp_str}"
            now = time.time()
            # Eski anahtarları temizle (10sn üstü)
            try:
                to_del = [k for k, t in self.recent_access_keys.items() if now - t > 10]
                for k in to_del:
                    self.recent_access_keys.pop(k, None)
            except Exception:
                pass
            if key in self.recent_access_keys and (now - self.recent_access_keys[key]) < 3:
                return True
            self.recent_access_keys[key] = now
            return False
        except Exception:
            return False

    def start_server(self, port=5002):
        try:
            self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.server_socket.bind(('0.0.0.0', port))
            self.server_socket.listen(5)
            self.running = True
            
            logger.info(f"TCP Server başlatıldı - Port: {port}")
            
            self.server_thread = threading.Thread(target=self._accept_connections)
            self.server_thread.daemon = True
            self.server_thread.start()
            
            # Periyodik getdeviceinfo thread'ini başlat
            self.periodic_getdeviceinfo_thread = threading.Thread(target=self._periodic_getdeviceinfo)
            self.periodic_getdeviceinfo_thread.daemon = True
            self.periodic_getdeviceinfo_thread.start()
            
            # TCP client bağlantılarını başlat
            self.start_tcp_client_connections()
            
        except Exception as e:
            logger.error(f"Server başlatma hatası: {e}")
            
    def _accept_connections(self):
        while self.running:
            try:
                client_socket, address = self.server_socket.accept()
                # IP kara listede ise sessizce kapat
                try:
                    if self.is_ip_blacklisted(address[0]):
                        try:
                            client_socket.close()
                        except Exception:
                            pass
                        # Sessiz mod: log basma
                        continue
                except Exception:
                    pass
                logger.info(f"Yeni bağlantı: {address}")
                
                client_thread = threading.Thread(
                    target=self._handle_client_connection,
                    args=(client_socket, address)
                )
                client_thread.daemon = True
                client_thread.start()
                
            except Exception as e:
                if self.running:
                    logger.error(f"Bağlantı kabul hatası: {e}")
                    
    def _save_device_info(self, device_id, ip_address, device_data):
        """Cihaz bilgilerini deviceinfo tablosuna kaydet"""
        try:
            conn = get_db_connection()
            
            # Önce mevcut kaydı kontrol et
            existing = conn.execute('SELECT id FROM deviceinfo WHERE device_id = ?', (device_id,)).fetchone()
            
            # Firmware bilgisini al
            try:
                firmware_version = (
                    (device_data.get('version')) or
                    (device_data.get('data', {}) or {}).get('version') or
                    'N/A'
                )
            except Exception:
                firmware_version = 'N/A'
            
            if existing:
                # Mevcut kaydı güncelle (getdeviceinfo JSON'unu DB'ye yazma)
                conn.execute('''
                    UPDATE deviceinfo 
                    SET ip_address = ?, device_data = ?, firmware_version = ?, last_update = CURRENT_TIMESTAMP, status = 'online'
                    WHERE device_id = ?
                ''', (ip_address, None, firmware_version, device_id))
            else:
                # Yeni kayıt ekle (getdeviceinfo JSON'unu DB'ye yazma)
                conn.execute('''
                    INSERT INTO deviceinfo (device_id, ip_address, device_data, firmware_version, status)
                    VALUES (?, ?, ?, ?, 'online')
                ''', (device_id, ip_address, None, firmware_version))
            
            conn.commit()
            conn.close()
            
        except Exception as e:
            logger.error(f"Device info kaydetme hatası: {e}")
            
    def _update_device_status(self, device_id, status):
        """Cihaz durumunu güncelle"""
        try:
            conn = get_db_connection()
            conn.execute('''
                UPDATE deviceinfo 
                SET status = ?, last_update = CURRENT_TIMESTAMP
                WHERE device_id = ?
            ''', (status, device_id))
            conn.commit()
            conn.close()
            logger.info(f"Device {device_id} durumu güncellendi: {status}")
        except Exception as e:
            logger.error(f"Device durum güncelleme hatası: {e}")
                    
    def _handle_client_connection(self, client_socket, address):
        device_id = None
        try:
            logger.info(f"Client bağlantısı işleniyor: {address}")
            
            # Direk getdeviceinfo gönder
            logger.info(f"Sending getdeviceinfo to {address}")
            client_socket.send("getdeviceinfo\n".encode('utf-8'))
            client_socket.settimeout(10.0)
            client_socket.settimeout(30.0)  # 30 saniye timeout - readlog için
            data = client_socket.recv(4096)  # Daha büyük buffer
            
            if data:
                response = data.decode('utf-8').strip()
                # Parse et, kayıtlı olup olmadığına göre log seviyesini belirle
                try:
                    json_response = json.loads(response)
                    device_id = json_response.get('deviceid')
                    
                    if device_id:
                        # Blacklist kontrolü
                        if self.is_blacklisted(device_id):
                            logger.debug(f"Karalistede cihaz: {device_id} @ {address[0]} - bağlantı düşürüldü")
                            try:
                                client_socket.close()
                            except Exception:
                                pass
                            return
                        # Cihaz kayıtlı mı kontrol et (cihazlar tablosu)
                        try:
                            conn_chk = get_db_connection(); conn_chk.row_factory = sqlite3.Row
                            row = conn_chk.execute('SELECT 1 FROM cihazlar WHERE id = ? OR seri_no = ? LIMIT 1', (str(device_id), str(device_id))).fetchone()
                            conn_chk.close()
                        except Exception as e:
                            row = None
                            logger.error(f"Kayıtsız cihaz kontrolü hatası: {e}")
                        if not row:
                            # Kayıtsız cihazlar için sessiz (debug) log; detay JSON'u yazma
                            logger.debug(f"Kayıtsız cihaz bağlantısı reddedildi: {device_id} @ {address[0]}")
                            # Deneme sayaçlarını artır
                            att = self.unregistered_attempts.get(device_id) or {'count':0,'first_ts':time.time(),'last_ts':0}
                            att['count'] += 1
                            att['last_ts'] = time.time()
                            self.unregistered_attempts[device_id] = att
                            # Eşik: 5 denemede 10 dakika karalisteye al
                            if att['count'] >= 5:
                                self.blacklist[device_id] = {'until': time.time()+600, 'reason':'unregistered_flood', 'ip': address[0]}
                                logger.info(f"Cihaz karalisteye alındı: {device_id} (10 dk)")
                            try:
                                # Kısa bir bilgi mesajı gönderebiliriz (opsiyonel)
                                client_socket.send(b'jsonfailed')
                            except Exception:
                                pass
                            try:
                                client_socket.close()
                            except Exception:
                                pass
                            return
                        # Kayıtlı cihazlar için yanıtı bilgi seviyesinde detaylı loglama (sessiz)
                        with self.lock:
                            self.client_connections[device_id] = client_socket
                            self.connected_device_ips[device_id] = address[0]
                        self._start_response_tracking(device_id)
                        # İlk bağlantıda response'u kaydet
                        self._record_device_response(device_id)
                        # Cihaz bilgilerini sadece cache'te tut (DB'ye yazma)
                        try:
                            with self.lock:
                                self.device_info_cache[str(device_id)] = {
                                    'data': json_response,
                                    'timestamp': time.time(),
                                    'ip_address': address[0]
                                }
                        except Exception:
                            pass
                        logger.info(f"Device ID doğrulandı: {device_id}")
                         
                        # Dinleme döngüsü
                        client_socket.settimeout(None)
                        while self.running and device_id in self.client_connections:
                            try:
                                data = client_socket.recv(4096)  # Daha büyük buffer
                                if not data:
                                    break
                                self._process_incoming_data(data, address, device_id)
                            except Exception as e:
                                logger.error(f"Veri alma hatası: {e}")
                                break
                except json.JSONDecodeError:
                    logger.warning(f"Geçersiz JSON: {response}")
                
        except Exception as e:
            logger.error(f"Client bağlantı hatası: {e}")
        finally:
            # Bağlantı kesildiğinde temizlik yap
            if device_id:
                with self.lock:
                    if device_id in self.client_connections:
                        del self.client_connections[device_id]
                    if device_id in self.connected_device_ips:
                        del self.connected_device_ips[device_id]
                # Cihaz durumunu offline yap
                self._update_device_status(device_id, 'offline')
                logger.info(f"Device {device_id} bağlantısı kesildi")
            
            try:
                client_socket.close()
            except:
                pass
            
    def _process_incoming_data(self, data, address, device_id=None):
        try:
            # Önce readlog komutundan gelen veriyi kontrol et
            # readlog komutu gönderildiyse tüm binary verileri topla
            if device_id and device_id in self.log_buffers:
                # readlog komutu aktif, tüm verileri topla
                # Veriyi buffer'a ekle
                self.log_buffers[device_id] += data
                
                # FILENOTFOUND kontrolü (log yoksa)
                if b'FILENOTFOUND' in self.log_buffers[device_id]:
                    logger.info(f"FILENOTFOUND TESPİT EDİLDİ: {device_id} - Cihazda log bulunmuyor")
                    
                    # Boş veri ile response oluştur
                    self.command_responses[device_id] = {
                        'status': 'success', 
                        'data': b'',  # Boş data
                        'message': 'Cihazda log bulunmuyor'
                    }
                    self._record_device_response(device_id)
                    
                    # Buffer'ı temizle
                    del self.log_buffers[device_id]
                    logger.info(f"LOG BUFFER TEMİZLENDİ (FILENOTFOUND): {device_id}")
                    return
                
                # UPLOADFINISHED geldi mi kontrol et
                elif b'UPLOADFINISHED' in self.log_buffers[device_id]:
                    logger.info(f"UPLOADFINISHED TESPİT EDİLDİ: {device_id}")
                    
                    # Tüm buffer'ı al
                    full_buffer = self.log_buffers[device_id]
                    logger.info(f"UPLOADFINISHED ALINDI - TÜM BUFFER BOYUTU: {device_id} -> {len(full_buffer)} byte")
                    
                    # Buffer'dan UPLOADSTARTED ve UPLOADFINISHED'ı çıkar
                    buffer_data = full_buffer
                    
                    # UPLOADSTARTED'ı çıkar
                    if b'UPLOADSTARTED' in buffer_data:
                        start_pos = buffer_data.find(b'UPLOADSTARTED') + len(b'UPLOADSTARTED')
                        buffer_data = buffer_data[start_pos:]
                    
                    # UPLOADFINISHED'ı çıkar
                    if b'UPLOADFINISHED' in buffer_data:
                        end_pos = buffer_data.find(b'UPLOADFINISHED')
                        buffer_data = buffer_data[:end_pos]
                    

                    
                    # Temizlenmiş veriyi command response'a kaydet
                    self.command_responses[device_id] = {
                        'status': 'success', 
                        'data': buffer_data,
                        'message': 'Log verisi tamamlandı'
                    }
                    self._record_device_response(device_id)
                    
                    # Buffer'ı temizle
                    del self.log_buffers[device_id]
                    logger.info(f"LOG BUFFER TEMİZLENDİ: {device_id}")
                else:
                    # Henüz UPLOADFINISHED gelmedi, devam et
                    self.command_responses[device_id] = {
                        'status': 'pending', 
                        'message': 'Log verisi toplanıyor...'
                    }
                return
            
            # Normal UTF-8 mesajları işle (getdeviceinfo vb.)
            try:
                message = data.decode('utf-8').strip()
                logger.info(f"JSON YANIT ALINDI: {device_id} -> '{message}'")
            except UnicodeDecodeError:
                # UTF-8 decode hatası - binary veri olabilir
                logger.warning(f"UTF-8 decode hatası, binary veri: {device_id} -> {len(data)} byte")
                logger.warning(f"BINARY VERİ HEX: {data.hex()}")
                
                # Binary veri geldiğinde de cihazı bağlı kabul et
                if device_id:
                    with self.lock:
                        if device_id not in self.device_info_cache:
                            self.device_info_cache[device_id] = {
                                'data': {'type': 'binary_data', 'size': len(data)},
                                'timestamp': time.time(),
                                'ip_address': address[0]
                            }
                    logger.info(f"Binary veri için cihaz cache'e eklendi: {device_id}")
                return
            
            if device_id:
                self._record_device_response(device_id)
                
                # ÖNCE: Her mesajda accessLog kontrolü yap (asenkron access log'lar için)
                if 'accessLog' in message:
                    logger.info(f"Cihaz {device_id} mesajında accessLog tespit edildi, işleniyor...")
                    # Birden fazla JSON ardışık yapışık gelebilir; hepsini sırayla işle
                    processed_any = False
                    for obj in self._iter_json_stream(message):
                        try:
                            if isinstance(obj, dict) and 'accessLog' in obj:
                                self._process_access_log(json.dumps(obj, ensure_ascii=False), device_id)
                                processed_any = True
                        except Exception as e:
                            logger.error(f"accessLog stream parselama hatası: {e}")
                    if processed_any:
                        # accessLog'lar işlendi; kalan akış için genel JSON parse aşamasına düşmeden çık
                        return
                    else:
                        # Tek JSON ise eski davranışa düş
                        self._process_access_log(message, device_id)
                
                # Komut yanıtlarını kontrol et
                if 'setok' in message.lower():
                    self.command_responses[device_id] = {'status': 'success', 'message': 'Komut başarıyla uygulandı'}
                    logger.info(f"Cihaz {device_id} komut yanıtı: BAŞARILI - setok")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                elif 'setfail' in message.lower():
                    self.command_responses[device_id] = {'status': 'error', 'message': 'Komut uygulanamadı'}
                    logger.info(f"Cihaz {device_id} komut yanıtı: BAŞARISIZ - setfail")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                elif 'setfailed' in message.lower():
                    self.command_responses[device_id] = {'status': 'error', 'message': 'Komut uygulanamadı'}
                    logger.info(f"Cihaz {device_id} komut yanıtı: BAŞARISIZ - setfailed")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                    
                    # setfailed yanıtında da veritabanından kartları sil (formatcards komutu gönderildiyse)
                    if 'formatcards' in str(self.command_responses.get(device_id, {}).get('message', '')).lower():
                        try:
                            conn = get_db_connection()
                            deleted_count = conn.execute('DELETE FROM device_cards WHERE device_id = ?', (device_id,)).rowcount
                            conn.commit()
                            conn.close()
                            logger.info(f"Cihaz {device_id} için {deleted_count} kart veritabanından silindi (setfailed - formatcards)")
                        except Exception as e:
                            logger.error(f"Veritabanından kart silme hatası (setfailed - formatcards): {e}")
                elif 'saveok' in message.lower() or 'jsonfailedsaveok' in message.lower():
                    self.command_responses[device_id] = {'status': 'success', 'message': 'Ayarlar başarıyla kaydedildi'}
                    logger.info(f"Cihaz {device_id} komut yanıtı: KAYDEDİLDİ - {message}")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                    
                    # Eğer formatcards komutu bekleniyorsa veritabanından kartları sil
                    if device_id in self.pending_formatcards and self.pending_formatcards[device_id]:
                        try:
                            conn = get_db_connection()
                            deleted_count = conn.execute('DELETE FROM device_cards WHERE device_id = ?', (device_id,)).rowcount
                            conn.commit()
                            conn.close()
                            logger.info(f"Cihaz {device_id} için {deleted_count} kart veritabanından silindi (saveok sonrası)")
                            
                            # Flag'i temizle
                            del self.pending_formatcards[device_id]
                        except Exception as e:
                            logger.error(f"Veritabanından kart silme hatası (saveok): {e}")
                elif 'savefailed' in message.lower():
                    self.command_responses[device_id] = {'status': 'error', 'message': 'Ayarlar kaydedilemedi'}
                    logger.info(f"Cihaz {device_id} komut yanıtı: KAYDEDİLEMEDİ - savefailed")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                elif 'ok' in message.lower() and len(message.strip()) <= 10:
                    self.command_responses[device_id] = {'status': 'success', 'message': 'Komut başarıyla uygulandı'}
                    logger.info(f"Cihaz {device_id} komut yanıtı: BAŞARILI - ok")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                elif 'formatcards' in message.lower():
                    self.command_responses[device_id] = {'status': 'success', 'message': 'Format cards komutu alındı'}
                    logger.info(f"Cihaz {device_id} komut yanıtı: FORMAT CARDS - {message}")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                    
                    # Card collection'ı temizle
                    if device_id in self.card_collection:
                        del self.card_collection[device_id]
                        logger.info(f"Cihaz {device_id} card collection temizlendi")
                    
                    # formatcards komutu gönderildiğini işaretle
                    self.pending_formatcards[device_id] = True
                    logger.info(f"Cihaz {device_id} için formatcards bekleniyor")
                    
                    # formatcards komutu alındığında hemen veritabanından kartları sil
                    try:
                        conn = get_db_connection()
                        deleted_count = conn.execute('DELETE FROM device_cards WHERE device_id = ?', (device_id,)).rowcount
                        conn.commit()
                        conn.close()
                        logger.info(f"Cihaz {device_id} için {deleted_count} kart veritabanından silindi (formatcards komutu alındığında)")
                    except Exception as e:
                        logger.error(f"Veritabanından kart silme hatası (formatcards): {e}")
                elif 'format' in message.lower():
                    self.command_responses[device_id] = {'status': 'success', 'message': 'Format komutu alındı'}
                    logger.info(f"Cihaz {device_id} komut yanıtı: FORMAT - {message}")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                    
                    # format komutu alındığında da veritabanından kartları sil
                    try:
                        conn = get_db_connection()
                        deleted_count = conn.execute('DELETE FROM device_cards WHERE device_id = ?', (device_id,)).rowcount
                        conn.commit()
                        conn.close()
                        logger.info(f"Cihaz {device_id} için {deleted_count} kart veritabanından silindi (format komutu alındığında)")
                    except Exception as e:
                        logger.error(f"Veritabanından kart silme hatası (format): {e}")
                elif 'updatecards' in message.lower():
                    self.command_responses[device_id] = {'status': 'success', 'message': 'Update cards komutu alındı'}
                    logger.info(f"Cihaz {device_id} komut yanıtı: UPDATE CARDS - {message}")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                elif 'addcards' in message.lower():
                    self.command_responses[device_id] = {'status': 'success', 'message': 'Add cards komutu alındı'}
                    logger.info(f"Cihaz {device_id} komut yanıtı: ADD CARDS - {message}")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                elif 'getcards' in message.lower() and 'readfinished' not in message.lower():
                    # getcards JSON yanıtını parse et ve kartları topla
                    try:
                        card_data = json.loads(message)
                        if 'getcards' in card_data and isinstance(card_data['getcards'], list):
                            # Kart koleksiyonunu başlat (eğer yoksa)
                            if device_id not in self.card_collection:
                                self.card_collection[device_id] = []
                            self.card_collection[device_id].extend(card_data['getcards'])
                            # DB'ye kaydetme devre dışı (yalnız cihazdan gelen anlık liste kullanılacak)
                            logger.info(f"Cihaz {device_id} getcards array'i alındı: {len(card_data['getcards'])} kart (db-yazma kapalı)")
                            logger.info(f"Cihaz {device_id} toplam toplanan kart: {len(self.card_collection[device_id])}")
                            logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                    except json.JSONDecodeError:
                        # JSON değilse normal mesaj olarak işle
                        self.command_responses[device_id] = {'status': 'success', 'message': 'Get cards komutu alındı'}
                        logger.info(f"Cihaz {device_id} komut yanıtı: GET CARDS - {message}")
                        logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                elif 'readstarted' in message.lower():
                    # Kart okuma başladı, kart koleksiyonunu sıfırla
                    self.card_collection[device_id] = []
                    logger.info(f"Cihaz {device_id} kart okuma başladı (koleksiyon sıfırlandı)")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                elif 'readfinished' in message.lower():
                    # Bazı cihazlar JSON'u '...}readfinished' olarak bitişik gönderir; JSON kısmını ayıkla ve işle
                    try:
                        lower_msg = message.lower()
                        idx = lower_msg.find('readfinished')
                        json_chunk = message[:idx].strip() if idx > 0 else ''
                        if json_chunk.startswith('{'):
                            try:
                                card_data = json.loads(json_chunk)
                                if 'getcards' in card_data and isinstance(card_data['getcards'], list):
                                    if device_id not in self.card_collection:
                                        self.card_collection[device_id] = []
                                    self.card_collection[device_id].extend(card_data['getcards'])
                                    # Veritabanına ekleme devre dışı: yalnız cihazdan anlık gösterim
                                    logger.info(f"Cihaz {device_id} getcards (concatenated) alındı: {len(card_data['getcards'])} kart (db-yazma kapalı)")
                            except Exception:
                                pass
                    except Exception:
                        pass
                    # Kart okuma bitti, toplanan kartları yanıt olarak gönder
                    cards = self.card_collection.get(device_id, [])
                    # Tekrarlı kartları kaldır (sıralı set)
                    try:
                        seen = set()
                        unique_cards = []
                        for k in cards:
                            key = str(k)
                            if key not in seen:
                                seen.add(key)
                                unique_cards.append(k)
                        cards = unique_cards
                    except Exception:
                        pass
                    self.command_responses[device_id] = {
                        'status': 'success', 
                        'message': 'Kart listesi alındı',
                        'cards': cards
                    }
                    logger.info(f"Cihaz {device_id} kart okuma bitti, {len(cards)} kart alındı (uniq)")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                elif 'nomorespace' in message.lower():
                    self.command_responses[device_id] = {'status': 'error', 'message': 'Yer yok'}
                    logger.info(f"Cihaz {device_id} komut yanıtı: NO MORE SPACE - {message}")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                elif 'accessLog' in message:
                    # Access log işleme (yedek - normalde yukarıda stream ile işlenir)
                    for obj in self._iter_json_stream(message):
                        try:
                            if isinstance(obj, dict) and 'accessLog' in obj:
                                self._process_access_log(json.dumps(obj, ensure_ascii=False), device_id)
                        except Exception as e:
                            logger.error(f"accessLog stream parselama hatası: {e}")
                    return
                elif 'format failed' in message.lower() or 'formatfailed' in message.lower():
                    self.command_responses[device_id] = {'status': 'success', 'message': 'Kullanıcıya zaten veri yok'}
                    logger.info(f"Cihaz {device_id} komut yanıtı: FORMAT FAILED - {message}")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                    
                    # formatfailed durumunda da veritabanından kartları sil
                    try:
                        conn = get_db_connection()
                        deleted_count = conn.execute('DELETE FROM device_cards WHERE device_id = ?', (device_id,)).rowcount
                        conn.commit()
                        conn.close()
                        logger.info(f"Cihaz {device_id} için {deleted_count} kart veritabanından silindi (formatfailed sonrası)")
                    except Exception as e:
                        logger.error(f"Veritabanından kart silme hatası (formatfailed): {e}")
                elif 'fail' in message.lower() and len(message.strip()) <= 10:
                    self.command_responses[device_id] = {'status': 'error', 'message': 'Komut uygulanamadı'}
                    logger.info(f"Cihaz {device_id} komut yanıtı: BAŞARISIZ - fail")
                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                else:
                    # readstarted ile readfinished arasındaki JSON array'leri parse et
                    if device_id in self.card_collection:
                        try:
                            # JSON array olup olmadığını kontrol et
                            if message.startswith('[') and message.endswith(']'):
                                card_data = json.loads(message)
                                if isinstance(card_data, list):
                                    self.card_collection[device_id].extend(card_data)
                                    logger.info(f"Cihaz {device_id} kart array'i alındı: {len(card_data)} kart")
                                    logger.info(f"Cihaz {device_id} RAW yanıt: {message}")
                        except json.JSONDecodeError:
                            # JSON değilse normal mesaj olarak işle
                            pass
                
            try:
                parsed_data = json.loads(message)
                
                # getdeviceinfo yanıtı kontrol et
                if parsed_data.get('type') == 'getdeviceinfo' and parsed_data.get('deviceid'):
                    response_device_id = parsed_data.get('deviceid')
                    
                    # getdeviceinfo verilerini cache'e kaydet (veritabanına yazma)
                    with self.lock:
                        self.device_info_cache[response_device_id] = {
                            'data': parsed_data,
                            'timestamp': time.time(),
                            'ip_address': address[0]
                        }
                    
                    # DB güncelleme devre dışı: yalnızca bellek içi cache kullanılacak
                    
                    # Bu cihaz ID'si veritabanında tanımlı mı kontrol et
                    is_registered = self._is_device_registered(response_device_id)
                    logger.info(f"Cihaz {response_device_id} kayıt durumu: {is_registered}")
                    
                    # Firmware ve IP bilgisini deviceinfo tablosunda güncelle
                    try:
                        if is_registered:
                            self._save_device_info(response_device_id, address[0], parsed_data)
                    except Exception as _sde:
                        logger.warning(f"deviceinfo güncelleme (getdeviceinfo akışı) hatası: {_sde}")
                    
                    # Sadece kayıtlı cihazlar için mastercard işle (bastırma penceresi yoksa)
                    if is_registered:
                        if self._is_mastercard_suppressed():
                            logger.info("MASTERCARD oluşturma bastırma penceresinde, atlandı")
                        else:
                            try:
                                mc = (
                                    parsed_data.get('mastercarduuid') or
                                    parsed_data.get('masterCardUuid') or
                                    parsed_data.get('master_card_uuid') or
                                    (parsed_data.get('data', {}) or {}).get('mastercarduuid')
                                )
                                if mc:
                                    # Sadece device_cards içinde isimlendirme/upsert yap, kullanıcı listesine ekleme
                                    self._upsert_mastercard(response_device_id, mc)
                            except Exception as mc_err:
                                logger.error(f"Mastercard işleme hatası: {mc_err}")
                    
                    if not is_registered:
                        logger.warning(f"Tanımlanmamış cihaz bağlantısı düşürülüyor: {response_device_id}")
                        
                        # Cihazın socket bağlantısını bul ve kapat
                        with self.lock:
                            if response_device_id in self.client_connections:
                                try:
                                    self.client_connections[response_device_id].close()
                                    logger.info(f"Tanımlanmamış cihaz socket bağlantısı kapatıldı: {response_device_id}")
                                except Exception as e:
                                    logger.error(f"Socket kapatma hatası {response_device_id}: {e}")
                            
                            # Bağlantı verilerini temizle
                            if response_device_id in self.client_connections:
                                del self.client_connections[response_device_id]
                            if response_device_id in self.connected_device_ips:
                                del self.connected_device_ips[response_device_id]
                            if response_device_id in self.device_response_status:
                                del self.device_response_status[response_device_id]
                            if response_device_id in self.command_responses:
                                del self.command_responses[response_device_id]
                            if response_device_id in self.rate_limit:
                                del self.rate_limit[response_device_id]
                            
                            logger.info(f"Tanımlanmamış cihaz bağlantısı tamamen temizlendi: {response_device_id}")
                            return  # İşlemi durdur
                
                # Genel JSON'ları DB'ye kaydetme - kaldırıldı
                # self._save_general_json_data(parsed_data, address)
            except json.JSONDecodeError:
                # Tek bir JSON olmayabilir; stream olarak dene
                parsed_any = False
                for obj in self._iter_json_stream(message):
                    try:
                        if isinstance(obj, dict) and obj.get('type') == 'getdeviceinfo' and obj.get('deviceid'):
                            response_device_id = obj.get('deviceid')
                            logger.info(f"getdeviceinfo (stream) alındı: {response_device_id}")
                            with self.lock:
                                self.device_info_cache[response_device_id] = {
                                    'data': obj,
                                    'timestamp': time.time(),
                                    'ip_address': address[0]
                                }
                            parsed_any = True
                    except Exception:
                        continue
                if not parsed_any:
                    self._save_simple_access_log(message, address)
                
        except Exception as e:
            logger.error(f"Veri işleme hatası: {e}")
            
    def _save_json_log(self, device_id, ip_address, data, data_type):
        """Cihazdan gelen JSON verileri json_data_logs tablosuna kaydet"""
        try:
            conn = get_db_connection()
            conn.execute('''
                CREATE TABLE IF NOT EXISTS json_data_logs (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    data_type TEXT,
                    json_data TEXT,
                    source_ip TEXT,
                    received_at DATETIME DEFAULT CURRENT_TIMESTAMP
                )
            ''')
            
            json_string = json.dumps(data, ensure_ascii=False, indent=2)
            conn.execute('''
                INSERT INTO json_data_logs (data_type, json_data, source_ip)
                VALUES (?, ?, ?)
            ''', (data_type, json_string, ip_address))
            
            conn.commit()
            conn.close()
            logger.info(f"JSON log kaydedildi: {data_type} - {device_id}")
            
        except Exception as e:
            logger.error(f"JSON log kayıt hatası: {e}")
    
    def _save_general_json_data(self, data, address):
        try:
            conn = get_db_connection()
            conn.execute('''
                CREATE TABLE IF NOT EXISTS json_data_logs (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    data_type TEXT,
                    json_data TEXT,
                    source_ip TEXT,
                    received_at DATETIME DEFAULT CURRENT_TIMESTAMP
                )
            ''')
            
            json_string = json.dumps(data, ensure_ascii=False, indent=2)
            conn.execute('''
                INSERT INTO json_data_logs (data_type, json_data, source_ip)
                VALUES (?, ?, ?)
            ''', ('general_json', json_string, address[0]))
            
            conn.commit()
            conn.close()
            
        except Exception as e:
            logger.error(f"JSON veri kayıt hatası: {e}")
    
    def _save_simple_access_log(self, message, address):
        try:
            conn = get_db_connection()
            conn.execute('''
                INSERT INTO gecisler (ad, bolge, gecis_turu, notlar, giris_durumu, zaman)
                VALUES (?, ?, ?, ?, ?, ?)
            ''', (
                'TCP Cihaz',
                'Genel',
                'ACCESS_TCPCLIENT',
                f'Mesaj: {message} - IP: {address[0]}',
                'GIRIS_YAPILDI',
                datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            ))
            
            conn.commit()
            conn.close()
            
        except Exception as e:
            logger.error(f"Basit log kayıt hatası: {e}")
    
    def _start_response_tracking(self, device_id):
        device_id_str = str(device_id)
        if device_id_str not in self.device_response_status:
            self.device_response_status[device_id_str] = {
                'last_response': None,
                'failed_attempts': 0,
                'last_ping_time': time.time()
            }
        else:
            self.device_response_status[device_id_str]['last_ping_time'] = time.time()
    
    def _record_device_response(self, device_id):
        device_id_str = str(device_id)
        if device_id_str not in self.device_response_status:
            self.device_response_status[device_id_str] = {
                'last_response': None,
                'failed_attempts': 0,
                'last_ping_time': time.time()
            }
        self.device_response_status[device_id_str]['last_response'] = time.time()
        self.device_response_status[device_id_str]['failed_attempts'] = 0
    
    def is_device_connected(self, device_id):
        device_id_str = str(device_id)
        # Aktif TCP client bağlantısı varsa bağlı kabul et
        if device_id_str in self.client_connections:
            return True
        # Son yanıt süresi (daha sıkı): 30 saniye
        if device_id_str in self.device_response_status:
            last_response = self.device_response_status[device_id_str].get('last_response')
            if last_response:
                if (time.time() - last_response) <= 30:
                    return True
            # Son ping'e göre (ör. komut gönderim denemeleri): 60 saniye
            last_ping = self.device_response_status[device_id_str].get('last_ping_time')
            if last_ping and (time.time() - last_ping) <= 60:
                return True
        # connected_device_ips sadece yakın ping varsa bağlı say
        if device_id_str in self.connected_device_ips and device_id_str in self.device_response_status:
            last_ping = self.device_response_status[device_id_str].get('last_ping_time')
            if last_ping and (time.time() - last_ping) <= 60:
                return True
        # device_info_cache üzerinden bağlı kabul etme (yanıltıcı olabilir)
        return False

    def get_device_info(self, device_id):
        """Cihazın deviceinfo verilerini getir"""
        try:
            conn = get_db_connection()
            device_info = conn.execute('''
                SELECT device_data FROM deviceinfo
                WHERE device_id = ?
            ''', (device_id,)).fetchone()
            conn.close()

            if device_info and device_info['device_data']:
                return json.loads(device_info['device_data'])
            return None
        except Exception as e:
            logger.error(f"Device info alma hatası {device_id}: {e}")
            return None
    
    def get_device_info_from_cache(self, device_id):
        """Cihaz bilgilerini cache'den al"""
        try:
            with self.lock:
                if device_id in self.device_info_cache:
                    return self.device_info_cache[device_id]
                else:
                    return None
        except Exception as e:
            logger.error(f"Cache'den device info alma hatası: {e}")
            return None

    def _upsert_mastercard(self, device_id: str, master_uuid: str):
        try:
            if not master_uuid:
                return
            uuid_str = str(master_uuid).strip()
            if not uuid_str:
                return
            conn = get_db_connection()
            # Mevcut durumu kontrol et
            existing = conn.execute('''
                SELECT user_name, terminal_mode_access FROM device_cards
                WHERE device_id = ? AND card_uuid = ?
            ''', (str(device_id), uuid_str)).fetchone()
            if existing is None:
                # Yoksa ekle
                try:
                    conn.execute('''
                        INSERT INTO device_cards (device_id, card_uuid, user_name, authority_zone, terminal_mode_access)
                        VALUES (?, ?, 'mastercard', '', 1)
                    ''', (str(device_id), uuid_str))
                    conn.commit()
                    logger.info(f"Mastercard kaydı eklendi: {device_id} -> {uuid_str}")
                except Exception as ie:
                    logger.error(f"device_cards INSERT hata: {ie}")
                finally:
                    conn.close()
                return
            # Varsa değişiklik gerekip gerekmediğini kontrol et
            needs_update = False
            if (existing['user_name'] or '').lower() != 'mastercard':
                needs_update = True
            if int(existing['terminal_mode_access'] or 0) != 1:
                needs_update = True
            if not needs_update:
                # Değişiklik yoksa yazma ve log atla
                try:
                    conn.close()
                except Exception:
                    pass
                return
            # Güncelle: isim ve erişim bayrağı
            conn.execute('''
                UPDATE device_cards
                SET user_name = 'mastercard', terminal_mode_access = 1
                WHERE device_id = ? AND card_uuid = ?
            ''', (str(device_id), uuid_str))
            conn.commit()
            conn.close()
            logger.info(f"Mastercard upsert edildi: {device_id} -> {uuid_str}")
        except Exception as e:
            logger.error(f"Mastercard upsert hata: {e}")

    def _ensure_mastercard_user(self, device_id: str, master_uuid: str):
        """Devre dışı: MASTERCARD kullanıcı kaydı oluşturma KAPALI."""
        return

    def get_all_device_info_from_cache(self):
        """Tüm cihaz bilgilerini cache'den al"""
        try:
            with self.lock:
                return dict(self.device_info_cache)
        except Exception as e:
            logger.error(f"Cache'den tüm device info alma hatası: {e}")
            return {}

    def suppress_mastercard(self, seconds: int = 5):
        """MASTERCARD otomatik oluşturmayı belirli süre bastır."""
        try:
            self.mastercard_suppress_until = time.time() + max(0, int(seconds))
            logger.info(f"MASTERCARD oluşturma {seconds} sn bastırıldı")
        except Exception as e:
            logger.error(f"suppress_mastercard hata: {e}")

    def _is_mastercard_suppressed(self) -> bool:
        try:
            return time.time() < self.mastercard_suppress_until
        except Exception:
            return False

    def _save_cards_to_db(self, device_id, cards):
        """Kartları veritabanına kaydet"""
        try:
            conn = get_db_connection()
            for card_uuid in cards:
                try:
                    conn.execute('''
                        INSERT OR IGNORE INTO device_cards (device_id, card_uuid, user_name, authority_zone, terminal_mode_access)
                        VALUES (?, ?, '', '', 0)
                    ''', (device_id, card_uuid))
                except Exception as e:
                    logger.error(f"Kart kaydetme hatası {card_uuid}: {e}")
            conn.commit()
            conn.close()
            logger.info(f"Cihaz {device_id} için {len(cards)} kart veritabanına kaydedildi")
        except Exception as e:
            logger.error(f"Kart kaydetme hatası {device_id}: {e}")

    def get_cards_from_db(self, device_id):
        """Veritabanından kartları getir"""
        try:
            conn = get_db_connection()
            cards = conn.execute('''
                SELECT id, card_uuid, user_name, authority_zone, terminal_mode_access, created_at
                FROM device_cards
                WHERE device_id = ?
                ORDER BY created_at DESC
            ''', (device_id,)).fetchall()
            conn.close()
            
            card_list = []
            for row in cards:
                card_list.append({
                    'id': row['id'],
                    'card_uuid': row['card_uuid'],
                    'user_name': row['user_name'] or '',
                    'authority_zone': row['authority_zone'] or '',
                    'terminal_mode_access': bool(row['terminal_mode_access']),
                    'created_at': row['created_at']
                })
            
            logger.info(f"Cihaz {device_id} için veritabanından {len(card_list)} kart alındı (yalnızca yönetim amaçlı)")
            return card_list
        except Exception as e:
            logger.error(f"Kart alma hatası {device_id}: {e}")
            return []

    def _process_access_log(self, message, device_id):
        """Access log'ları işle ve terminal modu kontrolü yap"""
        try:
            # Debug: Mesaj hash'i ile tekrar kontrol
            import hashlib
            msg_hash = hashlib.md5(message.encode()).hexdigest()[:8]
            logger.info(f"PROCESS ACCESS LOG START: {device_id} - Hash: {msg_hash}")
            # Mesajı temizle ve JSON kısmını bul
            clean_message = message
            
            # Eğer mesaj setok, saveok gibi komutlarla başlıyorsa temizle
            for prefix in ['setok', 'saveok', 'jsonfailedsaveok']:
                if clean_message.lower().startswith(prefix.lower()):
                    clean_message = clean_message[len(prefix):].strip()
                    logger.info(f"Cihaz {device_id} {prefix} mesajından JSON çıkarıldı: {clean_message}")
                    break
            
            # JSON parse et
            data = json.loads(clean_message)
            
            access_logs = data.get('accessLog', [])
            
            if not access_logs:
                logger.info(f"Cihaz {device_id} accessLog boş veya bulunamadı")
                return
            
            logger.info(f"Cihaz {device_id} için {len(access_logs)} access log işleniyor...")
            
            for log in access_logs:
                access_type_str = log.get('type', 'UNKNOWN')
                card_uuid = log.get('uuid', '')
                is_terminal = log.get('isterminal', False)
                is_granted = log.get('isgranted', False)
                timestamp = log.get('timestamp', 0)
                
                logger.info(f"Access log işleniyor: {device_id} - {access_type_str} - {card_uuid} - Terminal: {is_terminal} - Granted: {is_granted}")
                
                # Access type'ı sayısal değere çevir
                access_type_map = {
                    'NFC': 1,
                    'BUTTON': 2,
                    'TCPSERVER': 3,
                    'TCPCLIENT': 4,
                    'BLE': 5,
                    'RS485': 6
                }
                access_type = access_type_map.get(access_type_str, 0)
                
                # Terminal modda sistem kendi kontrolünü yapacak
                if is_terminal:
                    # Aynı UUID için tekrar kontrol önleme (kısa süre)
                    key = (str(device_id), str(card_uuid).strip().lower())
                    now_t = time.time()
                    last_t = self.terminal_action_recent.get(key, 0)
                    if now_t - last_t < 3.0:
                        logger.info(f"Terminal: Duplicate UUID skipped - {device_id} - {card_uuid}")
                        return
                    self.terminal_action_recent[key] = now_t
                    
                    # Sistem kendi erişim kontrolünü yap
                    system_granted = self._check_user_access_permissions(card_uuid, device_id)
                    
                    # Cihaza sistem kararını bildir
                    if system_granted:
                        try:
                            self.send_command_to_device(device_id, json.dumps({'grantaccess': card_uuid}))
                        except Exception:
                            self.send_command_to_device(device_id, f"grantaccess {card_uuid}")
                        logger.info(f"Terminal: GRANT -> {device_id} - {card_uuid}")
                    else:
                        try:
                            self.send_command_to_device(device_id, json.dumps({'denyaccess': card_uuid}))
                        except Exception:
                            self.send_command_to_device(device_id, f"denyaccess {card_uuid}")
                        logger.info(f"Terminal: DENY -> {device_id} - {card_uuid}")
                    
                    # Access log'u sistem kararı ile kaydet (cihaz kararını manipüle et)
                    self._save_access_log(device_id, access_type, card_uuid, is_terminal, system_granted, timestamp)
                    logger.info(f"Terminal: Access log kaydedildi - {device_id} - {card_uuid} - Granted: {system_granted}")
                else:
                    # Normal mod: cihaz kararını olduğu gibi kaydet
                    self._save_access_log(device_id, access_type, card_uuid, is_terminal, is_granted, timestamp)
                    
        except json.JSONDecodeError as e:
            logger.error(f"Access log JSON parse hatası: {message} - Hata: {e}")
        except Exception as e:
            logger.error(f"Access log işleme hatası: {e}")

    def _save_access_log(self, device_id, access_type, card_uuid, is_terminal, is_granted, timestamp):
        """Access log'u veritabanına kaydet"""
        try:
                
            # Bellek-içi hızlı dedup
            if self._is_duplicate_access(str(device_id), int(access_type), str(card_uuid or ''), str(timestamp)):
                logger.info(f"Access log tekrarı atlandı (in-memory): {device_id} - {card_uuid} - {timestamp}")
                return
            # DB seviyesinde aynı kayıt var mı kontrol et
            try:
                _conn = get_db_connection()
                _cur = _conn.cursor()
                _cur.execute('''SELECT 1 FROM access_logs WHERE device_id = ? AND access_type = ? AND card_uuid = ? AND timestamp = ? LIMIT 1''',
                             (device_id, access_type, card_uuid, timestamp))
                exists = _cur.fetchone() is not None
                _conn.close()
                if exists:
                    logger.info(f"Access log tekrarı atlandı (db-exists): {device_id} - {card_uuid} - {timestamp}")
                    return
            except Exception as _e:
                logger.warning(f"Access log duplicate kontrol hatası: {_e}")
            conn = get_db_connection()
            conn.execute('''
                INSERT INTO access_logs (device_id, access_type, card_uuid, is_terminal, is_granted, timestamp)
                VALUES (?, ?, ?, ?, ?, ?)
            ''', (device_id, access_type, card_uuid, 1 if is_terminal else 0, 1 if is_granted else 0, timestamp))
            conn.commit()
            conn.close()
            logger.info(f"Access log kaydedildi: {device_id} - {card_uuid} - Terminal: {is_terminal} - Granted: {is_granted}")

            # Kullanıcı bazlı tabloyu da besle (Mobil/TCP SERVER erişimleri için)
            try:
                if access_type == 3 and card_uuid:  # TCP SERVER (mobil)
                    # UUID'yi users tablosunda ara
                    uconn = sqlite3.connect('data.db')
                    uconn.row_factory = sqlite3.Row
                    ucur = uconn.cursor()
                    ucur.execute('SELECT id, full_name, user_type FROM users WHERE user_uuid = ?', (card_uuid,))
                    urow = ucur.fetchone()
                    if urow:
                        user_id = urow['id']
                        full_name = urow['full_name']
                        user_type = (urow['user_type'] or 'standard')
                        # Kullanıcının bu cihaz için user_devices kaydını bul (device_id=seri no)
                        ucur.execute('SELECT id, device_name FROM user_devices WHERE user_id = ? AND serial_number = ?', (user_id, device_id))
                        drow = ucur.fetchone()
                        uconn.close()
                        if drow:
                            user_device_id = drow['id']
                            device_name = drow['device_name']
                            # Daha önce burada sabit not yazılıyordu; artık user_access_logs'a burada otomatik kayıt eklemiyoruz.
                            pass
            except Exception as ue:
                logger.error(f"user_access_logs ekleme hatası: {ue}")
        except Exception as e:
            logger.error(f"Access log kaydetme hatası: {e}")

    def _check_user_access_permissions(self, card_uuid, device_id):
        """Kullanıcının erişim izinlerini kontrol et (pasiflik, zaman limiti, cihaz yetkisi)"""
        try:
            # UUID format dönüşümü: decimal <-> hex
            uuid_variants = set()
            uuid_raw = str(card_uuid or '').strip()
            uuid_variants.add(uuid_raw)
            
            try:
                # Eğer decimal ise hex'e çevir
                if uuid_raw.isdigit():
                    decimal_val = int(uuid_raw)
                    hex_val = format(decimal_val, 'x').upper()
                    uuid_variants.add(hex_val)
                    uuid_variants.add('0x' + hex_val)
                    uuid_variants.add(format(decimal_val, '08x').upper())
                    logger.info(f"UUID dönüşüm: {uuid_raw} (decimal) -> {hex_val} (hex)")
                # Eğer hex ise decimal'e çevir
                elif uuid_raw.startswith('0x') or all(c in '0123456789ABCDEFabcdef' for c in uuid_raw):
                    if uuid_raw.startswith('0x'):
                        hex_val = uuid_raw[2:]
                    else:
                        hex_val = uuid_raw
                    decimal_val = int(hex_val, 16)
                    uuid_variants.add(str(decimal_val))
                    logger.info(f"UUID dönüşüm: {uuid_raw} (hex) -> {decimal_val} (decimal)")
            except Exception as e:
                logger.warning(f"UUID format dönüşüm hatası: {e}")
            
            logger.info(f"UUID varyantları: {uuid_variants}")
            
            # Kullanıcıyı card_uuid ile bul
            user_data = None
            
            # Tüm UUID varyantları ile ara
            for uuid_variant in uuid_variants:
                if user_data:
                    break
                    
                # users.db'de ara
                try:
                    uconn = sqlite3.connect('users.db')
                    uconn.row_factory = sqlite3.Row
                    user_data = uconn.execute('''
                        SELECT id, full_name, is_active, time_limit_start, time_limit_end, user_type 
                        FROM users WHERE LOWER(card_uuid) = LOWER(?) AND card_uuid IS NOT NULL AND TRIM(card_uuid) != ""
                    ''', (uuid_variant,)).fetchone()
                    uconn.close()
                    if user_data:
                        logger.info(f"Kullanıcı users.db'de bulundu: {uuid_variant}")
                        break
                except Exception as e:
                    logger.error(f"users.db arama hatası ({uuid_variant}): {e}")
                
                # data.db'de ara
                try:
                    dconn = get_db_connection()
                    dconn.row_factory = sqlite3.Row
                    user_data = dconn.execute('''
                        SELECT id, full_name, is_active, time_limit_start, time_limit_end, user_type 
                        FROM users WHERE LOWER(card_uuid) = LOWER(?) AND card_uuid IS NOT NULL AND TRIM(card_uuid) != ""
                    ''', (uuid_variant,)).fetchone()
                    dconn.close()
                    if user_data:
                        logger.info(f"Kullanıcı data.db'de bulundu: {uuid_variant}")
                        break
                except Exception as e:
                    logger.error(f"data.db arama hatası ({uuid_variant}): {e}")
            
            if not user_data:
                logger.info(f"Kullanıcı bulunamadı: {card_uuid}")
                return False
            
            user_id = user_data['id']
            full_name = user_data['full_name']
            is_active = user_data['is_active']
            access_start_time = user_data['time_limit_start']
            access_end_time = user_data['time_limit_end']
            user_type = user_data['user_type']
            
            # 1. Pasiflik kontrolü
            if not is_active:
                logger.info(f"Kullanıcı pasif: {full_name} ({card_uuid})")
                return False
            
            # 2. Zaman limiti kontrolü
            if access_start_time or access_end_time:
                from datetime import datetime, time as dt_time
                import time
                
                now = datetime.now()
                current_time = now.time()
                
                if access_start_time:
                    try:
                        start_time = datetime.strptime(access_start_time, '%H:%M').time()
                        if current_time < start_time:
                            logger.info(f"Erişim zamanı henüz gelmedi: {full_name} ({card_uuid}) - Başlangıç: {access_start_time}")
                            return False
                    except Exception as e:
                        logger.warning(f"Başlangıç zamanı parse hatası: {access_start_time} - {e}")
                
                if access_end_time:
                    try:
                        end_time = datetime.strptime(access_end_time, '%H:%M').time()
                        if current_time > end_time:
                            logger.info(f"Erişim zamanı geçti: {full_name} ({card_uuid}) - Bitiş: {access_end_time}")
                            return False
                    except Exception as e:
                        logger.warning(f"Bitiş zamanı parse hatası: {access_end_time} - {e}")
            
            # 3. Cihaz yetkisi kontrolü
            device_access_granted = self._check_device_access_permission(user_id, device_id)
            if not device_access_granted:
                logger.info(f"Kullanıcının bu cihaza erişim yetkisi yok: {full_name} ({card_uuid}) - Device: {device_id}")
                return False
            
            logger.info(f"Kullanıcı erişim izni verildi: {full_name} ({card_uuid}) - Device: {device_id}")
            return True
            
        except Exception as e:
            logger.error(f"Kullanıcı erişim izni kontrolü hatası: {e}")
            return False
    
    def _check_device_access_permission(self, user_id, device_id):
        """Kullanıcının belirli cihaza erişim yetkisi var mı kontrol et"""
        try:
            # users.db'de user_device_access tablosunu kontrol et
            try:
                uconn = sqlite3.connect('users.db')
                uconn.row_factory = sqlite3.Row
                access_record = uconn.execute('''
                    SELECT 1 FROM user_device_access 
                    WHERE user_id = ? AND device_id = ?
                ''', (user_id, device_id)).fetchone()
                uconn.close()
                if access_record:
                    return True
            except Exception:
                pass
            
            # data.db'de user_device_access tablosunu kontrol et
            try:
                dconn = get_db_connection()
                dconn.row_factory = sqlite3.Row
                access_record = dconn.execute('''
                    SELECT 1 FROM user_device_access 
                    WHERE user_id = ? AND device_id = ?
                ''', (user_id, device_id)).fetchone()
                dconn.close()
                if access_record:
                    return True
            except Exception:
                pass
            
            return False
            
        except Exception as e:
            logger.error(f"Cihaz erişim yetkisi kontrolü hatası: {e}")
            return False

    def _save_terminal_access_log(self, device_id, card_uuid, access_type, is_terminal, is_granted):
        """Terminal mode access log'unu sistem tarafından kaydet"""
        try:
            import time
            timestamp = int(time.time())
            
            conn = get_db_connection()
            conn.execute('''
                INSERT INTO access_logs (device_id, access_type, card_uuid, is_terminal, is_granted, timestamp)
                VALUES (?, ?, ?, ?, ?, ?)
            ''', (device_id, access_type, card_uuid, 1 if is_terminal else 0, 1 if is_granted else 0, timestamp))
            conn.commit()
            conn.close()
            
            logger.info(f"Terminal access log kaydedildi (sistem): {device_id} - {card_uuid} - Access Type: {access_type} - Terminal: {is_terminal} - Granted: {is_granted}")
        except Exception as e:
            logger.error(f"Terminal access log kaydetme hatası: {e}")
    
    def send_command_to_device(self, device_id, command_string):
        """Cihaza komut gönderir ve yanıt bekler"""
        try:
            # Komut gönderme logu
            logger.info(f"Command -> {device_id}: {command_string}")
            
            
            device_id_str = str(device_id)
            
            # Önce TCP client bağlantılarında ara
            if device_id_str in self.tcp_client_connections:
                device_socket = self.tcp_client_connections[device_id_str]
                
                # Komut yanıtını temizle
                self.command_responses[device_id] = None
                
                # formatcards komutu gönderildiğinde flag'i set et
                if command_string.lower() == 'formatcards':
                    self.pending_formatcards[device_id] = True
                    logger.info(f"Cihaz {device_id} için formatcards komutu gönderildi (TCP client), saveok/setok bekleniyor")
                
                # readlog komutu gönderildiğinde buffer'ı başlat
                if command_string.lower() == 'readlog':
                    self.log_buffers[device_id] = b''
                    logger.info(f"Cihaz {device_id} için readlog komutu gönderildi (TCP client), log buffer başlatıldı")
                
                # Basit komut gönderimi
                command_to_send = command_string + "\n"
                command_bytes = command_to_send.encode('utf-8')
                device_socket.send(command_bytes)
                
                return True
            else:
                # TCP server bağlantılarında ara
                if device_id not in self.connected_device_ips:
                    logger.error(f"Cihaz {device_id} bağlı değil")
                    return False
                
                ip_address = self.connected_device_ips[device_id]
                device_socket = None
                
                for client_socket in self.client_connections.values():
                    try:
                        client_address = client_socket.getpeername()[0]
                        if client_address == ip_address:
                            device_socket = client_socket
                            break
                    except:
                        continue
                
                if not device_socket:
                    logger.error(f"Cihaz {device_id} socket'i bulunamadı")
                    return False
                
                # Komut yanıtını temizle
                self.command_responses[device_id] = None
                logger.info(f"Komut yanıtı temizlendi: {device_id}")
                
                # formatcards komutu gönderildiğinde flag'i set et
                if command_string.lower() == 'formatcards':
                    self.pending_formatcards[device_id] = True
                    logger.info(f"Cihaz {device_id} için formatcards komutu gönderildi, saveok/setok bekleniyor")
                
                # readlog komutu gönderildiğinde buffer'ı başlat
                if command_string.lower() == 'readlog':
                    self.log_buffers[device_id] = b''
                    logger.info(f"Cihaz {device_id} için readlog komutu gönderildi, log buffer başlatıldı")
                
                # TCP server için basit format (komut + newline)
                command_with_newline = command_string + "\n"
                command_bytes = command_with_newline.encode('utf-8')
                bytes_sent = device_socket.send(command_bytes)
                
                logger.info(f"TCP komut gönderildi: {device_id} -> {command_string} ({bytes_sent} byte)")
                return True
            
        except Exception as e:
            logger.error(f"Komut gönderme hatası: {e}")
            return False
    
    def get_command_response(self, device_id, timeout=5):
        """Komut yanıtını alır"""
        start_time = time.time()
        logger.info(f"Komut yanıtı bekleniyor: {device_id}, timeout: {timeout}")
        while time.time() - start_time < timeout:
            if device_id in self.command_responses and self.command_responses[device_id] is not None:
                response = self.command_responses[device_id]
                
                # Eğer pending durumundaysa devam et
                if response.get('status') == 'pending':
                    logger.debug(f"Log verisi toplanıyor: {device_id}")
                    time.sleep(0.1)  # 100ms bekle
                    continue
                
                # success veya error geldiğinde döndür ve temizle
                self.command_responses[device_id] = None  # Yanıtı temizle
                return response
            # Debug: Yanıt gelip gelmediğini kontrol et
            if device_id in self.command_responses:
                logger.debug(f"Yanıt bekleniyor: {device_id} -> {self.command_responses[device_id]}")
            # Kısa uyku ile aktif bekleme yapma
            time.sleep(0.05)
        logger.warning(f"Komut yanıtı timeout: {device_id}")
        return None
    
    def _periodic_getdeviceinfo(self):
        """Her 1 dakikada bir bağlı cihazlara getdeviceinfo gönder ve yetim cihazları temizle"""
        cleanup_counter = 0
        logger.info("Periyodik getdeviceinfo thread başlatıldı (1 dakika aralık)")
        while self.running:
            try:
                # UUID oluşturma sırasında tamamen durdur
                if self.pause_periodic_getdeviceinfo:
                    logger.info("Periyodik getdeviceinfo durduruldu - UUID oluşturma sırasında")
                    time.sleep(1)
                    continue
                
                with self.lock:
                    # Hem server hem client bağlantılarını al
                    server_devices = list(self.client_connections.keys())
                    client_devices = list(self.tcp_client_connections.keys())
                    connected_devices = server_devices + client_devices
                
                logger.info(f"Periyodik getdeviceinfo gönderiliyor - {len(connected_devices)} cihaz")
                
                for device_id in connected_devices:
                    try:
                        # getdeviceinfo komutunu gönder
                        success = self.send_command_to_device(device_id, "getdeviceinfo")
                        if not success:
                            logger.warning(f"Periyodik getdeviceinfo gönderilemedi: {device_id}")
                    except Exception as e:
                        logger.error(f"Periyodik getdeviceinfo hatası {device_id}: {e}")
                
                # Her 5 dakikada bir yetim cihazları temizle (5 * 1 dakika)
                cleanup_counter += 1
                if cleanup_counter >= 5:
                    logger.info("Yetim cihaz temizliği başlatılıyor...")
                    self.cleanup_disconnected_devices()
                    cleanup_counter = 0
                
                # 1 dakika bekle (60 saniye)
                time.sleep(60)
                
            except Exception as e:
                logger.error(f"Periyodik getdeviceinfo thread hatası: {e}")
                time.sleep(5)
    
    def connect_to_tcp_client_device(self, device_id, ip_address, port=80):
        """TCP client cihazına bağlanır"""
        try:
            device_id_str = str(device_id)
            
            # Eğer zaten bağlıysa, bağlantıyı kapat
            if device_id_str in self.tcp_client_connections:
                try:
                    self.tcp_client_connections[device_id_str].close()
                except:
                    pass
                del self.tcp_client_connections[device_id_str]
            
            # Yeni bağlantı oluştur
            client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            client_socket.settimeout(10.0)  # 10 saniye timeout
            client_socket.connect((ip_address, port))
            
            # Bağlantı başarılı, timeout ayarla
            client_socket.settimeout(30.0)  # 30 saniye timeout - readlog için
            
            # Bağlantıyı kaydet
            with self.lock:
                self.tcp_client_connections[device_id_str] = client_socket
                self.connected_device_ips[device_id_str] = ip_address
            
            # Response tracking başlat
            self._start_response_tracking(device_id)
            
            # Dinleme thread'ini başlat
            client_thread = threading.Thread(
                target=self._handle_tcp_client_connection,
                args=(client_socket, device_id, ip_address)
            )
            client_thread.daemon = True
            client_thread.start()
            
            self.tcp_client_threads[device_id_str] = client_thread
            
            return True
            
        except Exception as e:
            logger.error(f"TCP client bağlantı hatası {device_id}: {e}")
            return False
    
    def _handle_tcp_client_connection(self, client_socket, device_id, ip_address):
        """TCP client bağlantısını dinler"""
        device_id_str = str(device_id)
        try:
            # Bağlantı sonrası kısa bekle
            time.sleep(1)
            
            # Şimdi getdeviceinfo gönder
            client_socket.settimeout(30.0)
            self.send_command_to_device(device_id, "getdeviceinfo")
            
            while self.running and device_id_str in self.tcp_client_connections:
                try:
                    data = client_socket.recv(4096)  # Daha büyük buffer
                    if not data:
                        break
                    
                    # Gelen veriyi işle
                    self._process_incoming_data(data, (ip_address, 0), device_id)
                    
                except socket.timeout:
                    continue  # Timeout olursa devam et, bağlantıyı koparmayacağız
                except Exception as e:
                    logger.error(f"TCP client veri alma hatası {device_id}: {e}")
                    break
                    
        except Exception as e:
            logger.error(f"TCP client bağlantı hatası {device_id}: {e}")
        finally:
            # Bağlantı kesildiğinde temizlik yap
            with self.lock:
                if device_id_str in self.tcp_client_connections:
                    del self.tcp_client_connections[device_id_str]
                if device_id_str in self.connected_device_ips:
                    del self.connected_device_ips[device_id_str]
                if device_id_str in self.tcp_client_threads:
                    del self.tcp_client_threads[device_id_str]
            
            # Cihaz durumunu offline yap
            self._update_device_status(device_id, 'offline')
            
            try:
                client_socket.close()
            except:
                pass
            
            # 5 saniye sonra otomatik yeniden bağlan
            if self.running:
                threading.Timer(5.0, self._reconnect_tcp_client, args=[device_id, ip_address]).start()
    
    def _reconnect_tcp_client(self, device_id, ip_address):
        """TCP client cihazına yeniden bağlanır"""
        try:
            device_id_str = str(device_id)
            
            # Eğer zaten bağlıysa, yeniden bağlanma
            if device_id_str in self.tcp_client_connections:
                return
            
            # Cihazın hala kayıtlı olup olmadığını kontrol et
            if not self._is_device_registered(device_id):
                return
            
            # Yeniden bağlan
            success = self.connect_to_tcp_client_device(device_id, ip_address, 80)
            if not success:
                # Başarısız olursa 30 saniye sonra tekrar dene
                if self.running:
                    threading.Timer(30.0, self._reconnect_tcp_client, args=[device_id, ip_address]).start()
                    
        except Exception as e:
            logger.error(f"TCP client yeniden bağlanma hatası {device_id}: {e}")
            # Hata durumunda 30 saniye sonra tekrar dene
            if self.running:
                threading.Timer(30.0, self._reconnect_tcp_client, args=[device_id, ip_address]).start()
    
    def restart_tcp_client_connections(self):
        """TCP client bağlantılarını yeniden başlatır"""
        logger.info("TCP client bağlantıları yeniden başlatılıyor...")
        self.start_tcp_client_connections()
    
    def clear_device_cache(self):
        """Tüm cihaz cache'ini temizle"""
        with self.lock:
            self.connected_device_ips.clear()
            self.device_response_status.clear()
            self.device_info_cache.clear()
            logger.info("Device cache cleared")
    
    def start_tcp_client_connections(self):
        """Veritabanındaki TCP client cihazlara bağlanır"""
        try:
            conn = get_db_connection()
            
            # Ana cihazlar tablosundan TCP client cihazları al
            tcp_client_devices = conn.execute('''
                SELECT id, ip_adresi FROM cihazlar 
                WHERE tcp_tipi = 'client'
            ''').fetchall()
            
            # Artık tüm cihazlar ana database'de, ayrı user_devices tablosuna gerek yok
            logger.info("All devices are now in main database only")
            
            conn.close()
            
            # Kendi IP adresini al
            import socket
            try:
                # Kendi IP adresini al
                s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                s.connect(("8.8.8.8", 80))
                own_ip = s.getsockname()[0]
                s.close()
            except:
                own_ip = "127.0.0.1"
            
            # Kendi IP adresine bağlanmayı engelle ve duplicate cihazları filtrele
            filtered_devices = []
            seen_devices = set()  # Duplicate kontrolü için
            
            for device in tcp_client_devices:
                device_id = device['id']
                ip_address = device['ip_adresi']
                
                # Duplicate kontrol
                if device_id in seen_devices:
                    logger.warning(f"Duplicate cihaz engellendi: {device_id} -> {ip_address}")
                    continue
                
                if ip_address and ip_address != 'None' and ip_address.strip():
                    if ip_address == own_ip:
                        logger.warning(f"Kendi IP adresine bağlanma engellendi: {device_id} -> {ip_address}")
                    else:
                        filtered_devices.append(device)
                        seen_devices.add(device_id)
                else:
                    logger.warning(f"TCP client cihazının IP adresi yok veya geçersiz: {device_id} -> {ip_address}")
            
            logger.info(f"Filtrelenmiş {len(filtered_devices)} TCP client cihazına bağlanma deneniyor")
            
            # Her TCP client cihazına bağlan
            for device in filtered_devices:
                device_id = device['id']
                ip_address = device['ip_adresi']
                
                logger.info(f"TCP client cihazına bağlanma deneniyor: {device_id} -> {ip_address}:80")
                # Bağlantıyı ayrı thread'de dene
                connect_thread = threading.Thread(
                    target=self.connect_to_tcp_client_device,
                    args=(device_id, ip_address, 80)
                )
                connect_thread.daemon = True
                connect_thread.start()
                    
        except Exception as e:
            logger.error(f"TCP client bağlantıları başlatma hatası: {e}")
    
    def disconnect_device(self, device_id):
        """Belirli bir cihazın bağlantısını keser ve tüm verilerini temizler"""
        device_id_str = str(device_id)
        try:
            with self.lock:
                # TCP Server bağlantısını kapat
                if device_id_str in self.client_connections:
                    try:
                        self.client_connections[device_id_str].close()
                    except:
                        pass
                    
                    # Bağlantı verilerini temizle
                    del self.client_connections[device_id_str]
                
                # TCP Client bağlantısını kapat
                if device_id_str in self.tcp_client_connections:
                    try:
                        self.tcp_client_connections[device_id_str].close()
                    except:
                        pass
                    
                    # Bağlantı verilerini temizle
                    del self.tcp_client_connections[device_id_str]
                
                # TCP Client thread'ini temizle
                if device_id_str in self.tcp_client_threads:
                    del self.tcp_client_threads[device_id_str]
                
                # IP adresini temizle
                if device_id_str in self.connected_device_ips:
                    del self.connected_device_ips[device_id_str]
                
                # Yanıt durumunu temizle
                if device_id_str in self.device_response_status:
                    del self.device_response_status[device_id_str]
                
                # Komut yanıtını temizle
                if device_id_str in self.command_responses:
                    del self.command_responses[device_id_str]
                
                # Rate limit verilerini temizle
                if device_id_str in self.rate_limit:
                    del self.rate_limit[device_id_str]
                
                logger.info(f"Cihaz bağlantısı tamamen temizlendi: {device_id}")
                
        except Exception as e:
            logger.error(f"Cihaz bağlantısı temizleme hatası {device_id}: {e}")
    
    def cleanup_disconnected_devices(self):
        """Veritabanında olmayan cihazların bağlantılarını temizler"""
        try:
            # Veritabanından mevcut cihazları al
            conn = get_db_connection()
            db_devices = set()
            
            # Ana cihazlar tablosundan
            main_devices = conn.execute('SELECT id FROM cihazlar').fetchall()
            for device in main_devices:
                db_devices.add(str(device['id']))
            
            # Kullanıcı cihazlarından
            try:
                from user_management import UserManager
                user_manager = UserManager()
                user_devices = user_manager.get_all_user_devices()
                for device in user_devices:
                    db_devices.add(str(device['serial_number']))
            except Exception as e:
                logger.error(f"Kullanıcı cihazları getirme hatası: {e}")
            
            conn.close()
            
            # Bağlı olan ama veritabanında olmayan cihazları bul
            with self.lock:
                connected_devices = set(self.client_connections.keys())
                orphaned_devices = connected_devices - db_devices
            
            # Yetim cihazları temizle
            for device_id in orphaned_devices:
                logger.info(f"Yetim cihaz temizleniyor: {device_id}")
                self.disconnect_device(device_id)
                
        except Exception as e:
            logger.error(f"Yetim cihaz temizleme hatası: {e}")
    
    def _is_device_registered(self, device_id):
        """Cihazın veritabanında tanımlı olup olmadığını kontrol eder"""
        try:
            conn = get_db_connection()
            
            # Ana cihazlar tablosunda ara
            main_device = conn.execute('SELECT id FROM cihazlar WHERE id = ?', (device_id,)).fetchone()
            if main_device:
                conn.close()
                return True
            
            # Kullanıcı cihazlarında ara
            try:
                from user_management import UserManager
                user_manager = UserManager()
                user_devices = user_manager.get_all_user_devices()
                for device in user_devices:
                    if str(device['serial_number']) == str(device_id):
                        logger.info(f"Cihaz kullanıcı tablosunda bulundu: {device_id}")
                        conn.close()
                        return True
            except Exception as e:
                logger.error(f"Kullanıcı cihazları kontrol hatası: {e}")
            
            logger.warning(f"Cihaz hiçbir tabloda bulunamadı: {device_id}")
            conn.close()
            return False
            
        except Exception as e:
            logger.error(f"Cihaz kayıt kontrol hatası {device_id}: {e}")
            return False
                
    def stop(self):
        logger.info("TCP Connection Manager durduruluyor...")
        self.running = False
        
        if self.server_socket:
            try:
                self.server_socket.close()
            except Exception as e:
                logger.error(f"Server socket kapatma hatası: {e}")
            
        for device_id, client_socket in self.client_connections.items():
            try:
                client_socket.close()
            except Exception as e:
                logger.error(f"Client socket kapatma hatası: {e}")
        
        self.client_connections.clear()
        self.client_threads.clear()
        self.connected_device_ips.clear()
        self.device_response_status.clear()
        
        logger.info("TCP Connection Manager durduruldu")
    

# Global TCP Manager instance
tcp_manager = TCPConnectionManager() 