#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Secure Chat Server
Uçtan uca şifreli mesajlaşma sunucusu
Oracle Cloud'da çalıştırılmak üzere tasarlanmıştır.
IP: 141.144.240.14
Port: 65432
"""

import socket
import threading
import json
import sqlite3
import hashlib
import secrets
import time
import os
import sys
import base64
from datetime import datetime
from collections import defaultdict

from cryptography.fernet import Fernet
# Şifreleme için (sadece hash için kullanılır)
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend

# ============================================================================
# SABİTLER
# ============================================================================
SERVER_IP = "0.0.0.0"          # Tüm arayüzlerde dinle
SERVER_PORT = 6543
DB_FILE = "chat_server.db"
MAX_MESSAGE_HISTORY = 100      # Kullanıcı başına saklanacak maksimum mesaj sayısı
SESSION_TIMEOUT = 3600          # 1 saat (saniye)

# Renkli terminal çıktısı (isteğe bağlı)
class Colors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'

# ============================================================================
# VERİTABANI YÖNETİCİSİ
# ============================================================================
class DatabaseManager:
    def __init__(self, db_file=DB_FILE):
        self.db_file = db_file
        self.init_db()

    def get_connection(self):
        """Thread-safe veritabanı bağlantısı"""
        return sqlite3.connect(self.db_file, check_same_thread=False)

    def init_db(self):
        """Tabloları oluştur"""
        conn = self.get_connection()
        c = conn.cursor()

        # Kullanıcılar tablosu
        c.execute('''
            CREATE TABLE IF NOT EXISTS users (
                username TEXT PRIMARY KEY,
                password_hash TEXT NOT NULL,
                public_key TEXT,
                session_token TEXT,
                last_seen REAL,
                is_online INTEGER DEFAULT 0,
                created_at REAL
            )
        ''')

        # Mesajlar tablosu
        c.execute('''
            CREATE TABLE IF NOT EXISTS messages (
                message_uuid TEXT PRIMARY KEY,
                sender_username TEXT NOT NULL,
                receiver_username TEXT,
                group_uuid TEXT,
                content TEXT NOT NULL,
                encrypted_key TEXT,
                message_type TEXT DEFAULT 'text',
                reply_to TEXT,
                status TEXT DEFAULT 'sent',
                timestamp REAL,
                FOREIGN KEY (sender_username) REFERENCES users(username)
            )
        ''')

        # Gruplar tablosu
        c.execute('''
            CREATE TABLE IF NOT EXISTS groups (
                group_uuid TEXT PRIMARY KEY,
                group_name TEXT NOT NULL,
                description TEXT,
                admin_username TEXT NOT NULL,
                group_key TEXT,
                created_at REAL,
                FOREIGN KEY (admin_username) REFERENCES users(username)
            )
        ''')

        # Grup üyeleri tablosu
        c.execute('''
            CREATE TABLE IF NOT EXISTS group_members (
                group_uuid TEXT NOT NULL,
                username TEXT NOT NULL,
                joined_at REAL,
                role TEXT DEFAULT 'member',
                PRIMARY KEY (group_uuid, username),
                FOREIGN KEY (group_uuid) REFERENCES groups(group_uuid),
                FOREIGN KEY (username) REFERENCES users(username)
            )
        ''')

        # Reaksiyonlar tablosu
        c.execute('''
            CREATE TABLE IF NOT EXISTS reactions (
                message_uuid TEXT NOT NULL,
                username TEXT NOT NULL,
                reaction TEXT NOT NULL,
                timestamp REAL,
                PRIMARY KEY (message_uuid, username),
                FOREIGN KEY (message_uuid) REFERENCES messages(message_uuid),
                FOREIGN KEY (username) REFERENCES users(username)
            )
        ''')

        # Grup üyelerine özel şifreli grup anahtarları
        c.execute('''
            CREATE TABLE IF NOT EXISTS group_member_keys (
                group_uuid TEXT NOT NULL,
                username TEXT NOT NULL,
                encrypted_group_key TEXT NOT NULL,
                PRIMARY KEY (group_uuid, username),
                FOREIGN KEY (group_uuid) REFERENCES groups(group_uuid),
                FOREIGN KEY (username) REFERENCES users(username)
            )
        ''')

        conn.commit()
        conn.close()
        print(f"[DB] Veritabanı başlatıldı: {self.db_file}")  # self.db_file düzeltildi

    # ----- Kullanıcı işlemleri -----
    def register_user(self, username, password, public_key):
        """Yeni kullanıcı kaydet. Başarılı ise True, kullanıcı varsa False döndür."""
        conn = self.get_connection()
        c = conn.cursor()

        # Kullanıcı var mı?
        c.execute("SELECT username FROM users WHERE username = ?", (username,))
        if c.fetchone():
            conn.close()
            return False

        # Şifreyi hashle (PBKDF2)
        salt = secrets.token_bytes(16)
        kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000)
        password_hash = salt.hex() + ':' + kdf.derive(password.encode()).hex()

        # Kaydet
        c.execute('''
            INSERT INTO users (username, password_hash, public_key, created_at, last_seen)
            VALUES (?, ?, ?, ?, ?)
        ''', (username, password_hash, public_key, time.time(), time.time()))

        conn.commit()
        conn.close()
        return True

    def authenticate_user(self, username, password):
        """Kullanıcı adı ve şifreyi doğrula. Başarılı ise True, değilse False."""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute("SELECT password_hash FROM users WHERE username = ?", (username,))
        row = c.fetchone()
        conn.close()

        if not row:
            return False

        stored = row[0]
        try:
            salt_hex, hash_hex = stored.split(':')
            salt = bytes.fromhex(salt_hex)
            stored_hash = bytes.fromhex(hash_hex)

            kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000)
            computed_hash = kdf.derive(password.encode())
            return computed_hash == stored_hash
        except Exception:
            return False

    def set_session_token(self, username):
        """Yeni bir session token oluştur ve veritabanına kaydet."""
        token = secrets.token_urlsafe(32)
        conn = self.get_connection()
        c = conn.cursor()
        c.execute("UPDATE users SET session_token = ?, last_seen = ?, is_online = 1 WHERE username = ?",
                  (token, time.time(), username))
        conn.commit()
        conn.close()
        return token

    def validate_session(self, username, token):
        """Token geçerli mi ve süresi dolmamış mı?"""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute("SELECT session_token, last_seen FROM users WHERE username = ?", (username,))
        row = c.fetchone()
        conn.close()

        if not row or row[0] != token:
            return False

        last_seen = row[1]
        if time.time() - last_seen > SESSION_TIMEOUT:
            return False

        return True

    def update_last_seen(self, username):
        """Kullanıcının son görülme zamanını güncelle."""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute("UPDATE users SET last_seen = ? WHERE username = ?", (time.time(), username))
        conn.commit()
        conn.close()

    def set_online(self, username, online=True):
        """Kullanıcının çevrimiçi durumunu güncelle."""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute("UPDATE users SET is_online = ?, last_seen = ? WHERE username = ?",
                  (1 if online else 0, time.time(), username))
        conn.commit()
        conn.close()

    def get_online_users(self):
        """Çevrimiçi kullanıcıların listesini döndür."""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute("SELECT username FROM users WHERE is_online = 1")
        rows = c.fetchall()
        conn.close()
        return [row[0] for row in rows]

    def get_all_users(self):
        """Tüm kayıtlı kullanıcıları döndür."""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute("SELECT username, is_online FROM users ORDER BY username")
        rows = c.fetchall()
        conn.close()
        return [{'username': row[0], 'online': bool(row[1])} for row in rows]

    def get_user_public_key(self, username):
        """Kullanıcının public key'ini döndür."""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute("SELECT public_key FROM users WHERE username = ?", (username,))
        row = c.fetchone()
        conn.close()
        return row[0] if row else None

    def search_users(self, query):
        """Kullanıcı adında query geçen kullanıcıları getir (çevrimiçi durumuyla)."""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute('''
            SELECT username, is_online FROM users
            WHERE username LIKE ? ORDER BY username
        ''', (f'%{query}%',))
        rows = c.fetchall()
        conn.close()
        return [{'username': r[0], 'online': bool(r[1])} for r in rows]

    # ----- Mesaj işlemleri -----
    def save_message(self, msg_data):
        """Mesajı veritabanına kaydet. message_uuid unique olmalı."""
        conn = self.get_connection()
        c = conn.cursor()
        try:
            c.execute('''
                INSERT INTO messages (
                    message_uuid, sender_username, receiver_username, group_uuid,
                    content, encrypted_key, message_type, reply_to, status, timestamp
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ''', (
                msg_data['message_uuid'],
                msg_data['sender'],
                msg_data.get('receiver'),
                msg_data.get('group_uuid'),
                msg_data['content'],
                msg_data.get('encrypted_key'),
                msg_data.get('message_type', 'text'),
                msg_data.get('reply_to'),
                msg_data.get('status', 'sent'),
                msg_data.get('timestamp', time.time())
            ))
            conn.commit()
        except sqlite3.IntegrityError:
            print(f"[DB] Mesaj UUID çakışması: {msg_data['message_uuid']}")
        finally:
            conn.close()

    def get_chat_history(self, username, other_user=None, group_uuid=None, limit=100):
        """İki kullanıcı arasındaki veya bir gruptaki mesaj geçmişini getir."""
        conn = self.get_connection()
        c = conn.cursor()

        if group_uuid:
            c.execute('''
                SELECT * FROM messages
                WHERE group_uuid = ?
                ORDER BY timestamp DESC LIMIT ?
            ''', (group_uuid, limit))
        elif other_user:
            c.execute('''
                SELECT * FROM messages
                WHERE (sender_username = ? AND receiver_username = ?)
                   OR (sender_username = ? AND receiver_username = ?)
                ORDER BY timestamp DESC LIMIT ?
            ''', (username, other_user, other_user, username, limit))
        else:
            conn.close()
            return []

        # Sütun isimlerini al
        col_names = [description[0] for description in c.description]
        rows = c.fetchall()
        conn.close()

        messages = []
        for row in rows:
            msg = dict(zip(col_names, row))
            # Tarih formatını düzelt
            if isinstance(msg['timestamp'], (int, float)):
                msg['timestamp'] = datetime.fromtimestamp(msg['timestamp']).isoformat()
            messages.append(msg)

        return messages

    def update_message_status(self, message_uuid, status):
        """Mesaj durumunu güncelle (delivered, read)."""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute("UPDATE messages SET status = ? WHERE message_uuid = ?", (status, message_uuid))
        conn.commit()
        conn.close()

    # ----- Reaksiyonlar -----
    def add_reaction(self, message_uuid, username, reaction):
        """Mesaja reaksiyon ekle."""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute('''
            INSERT OR REPLACE INTO reactions (message_uuid, username, reaction, timestamp)
            VALUES (?, ?, ?, ?)
        ''', (message_uuid, username, reaction, time.time()))
        conn.commit()
        conn.close()

    def get_reactions(self, message_uuid):
        """Bir mesajdaki tüm reaksiyonları getir."""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute("SELECT username, reaction FROM reactions WHERE message_uuid = ?", (message_uuid,))
        rows = c.fetchall()
        conn.close()
        return [{'username': r[0], 'reaction': r[1]} for r in rows]

    # ----- Grup işlemleri -----
    def create_group(self, group_uuid, group_name, description, admin_username, members):
        """Yeni grup oluştur ve üyeleri ekle."""
        conn = self.get_connection()
        c = conn.cursor()

        group_key = Fernet.generate_key()

        # Grup tablosuna ekle
        c.execute('''
            INSERT INTO groups (group_uuid, group_name, description, admin_username, group_key, created_at)
            VALUES (?, ?, ?, ?, ?, ?)
        ''', (group_uuid, group_name, description, admin_username, group_key.decode('utf-8'), time.time()))

        # Üyeleri kaydet ve onlara grup anahtarını şifrele
        for member in set(members + [admin_username]):
            role = 'admin' if member == admin_username else 'member'
            c.execute('''
                INSERT OR IGNORE INTO group_members (group_uuid, username, joined_at, role)
                VALUES (?, ?, ?, ?)
            ''', (group_uuid, member, time.time(), role))

            public_key = self.get_user_public_key(member)
            if public_key:
                receiver_public_key = serialization.load_pem_public_key(
                    public_key.encode('utf-8'),
                    backend=default_backend()
                )
                encrypted_group_key = receiver_public_key.encrypt(
                    group_key,
                    padding.OAEP(
                        mgf=padding.MGF1(algorithm=hashes.SHA256()),
                        algorithm=hashes.SHA256(),
                        label=None
                    )
                )
                encrypted_group_key_b64 = base64.b64encode(encrypted_group_key).decode('utf-8')
                c.execute('''
                    INSERT OR REPLACE INTO group_member_keys (group_uuid, username, encrypted_group_key)
                    VALUES (?, ?, ?)
                ''', (group_uuid, member, encrypted_group_key_b64))

        conn.commit()
        conn.close()

    def get_user_groups(self, username):
        """Kullanıcının üye olduğu grupları getir."""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute('''
            SELECT g.group_uuid, g.group_name, g.description, g.admin_username
            FROM groups g
            JOIN group_members gm ON g.group_uuid = gm.group_uuid
            WHERE gm.username = ?
        ''', (username,))
        rows = c.fetchall()

        groups = []
        for row in rows:
            group_uuid = row[0]
            c.execute('''
                SELECT username, role FROM group_members WHERE group_uuid = ?
            ''', (group_uuid,))
            members = [{'username': m[0], 'role': m[1]} for m in c.fetchall()]
            groups.append({
                'group_uuid': group_uuid,
                'group_name': row[1],
                'description': row[2],
                'admin': row[3],
                'members': members
            })
        conn.close()
        return groups

    def is_group_member(self, group_uuid, username):
        """Kullanıcı grubun üyesi mi?"""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute('''
            SELECT 1 FROM group_members WHERE group_uuid = ? AND username = ?
        ''', (group_uuid, username))
        result = c.fetchone() is not None
        conn.close()
        return result

    def get_group_key_for_user(self, group_uuid, username):
        """Kullanıcıya ait grubun şifreli anahtarını getir."""
        conn = self.get_connection()
        c = conn.cursor()
        c.execute('''
            SELECT encrypted_group_key FROM group_member_keys
            WHERE group_uuid = ? AND username = ?
        ''', (group_uuid, username))
        row = c.fetchone()
        conn.close()
        return row[0] if row else None

    def add_group_member(self, group_uuid, new_member, role='member'):
        """Gruba yeni üye ekle ve ona grup anahtarını şifrele."""
        conn = self.get_connection()
        c = conn.cursor()

        # Yeni üyeyi gruba ekle
        c.execute('''
            INSERT OR IGNORE INTO group_members (group_uuid, username, joined_at, role)
            VALUES (?, ?, ?, ?)
        ''', (group_uuid, new_member, time.time(), role))

        # Mevcut grup anahtarını al
        c.execute('''
            SELECT group_key FROM groups WHERE group_uuid = ?
        ''', (group_uuid,))
        row = c.fetchone()
        if row and row[0]:
            group_key = row[0].encode('utf-8')
            public_key = self.get_user_public_key(new_member)
            if public_key:
                receiver_public_key = serialization.load_pem_public_key(
                    public_key.encode('utf-8'),
                    backend=default_backend()
                )
                encrypted_group_key = receiver_public_key.encrypt(
                    group_key,
                    padding.OAEP(
                        mgf=padding.MGF1(algorithm=hashes.SHA256()),
                        algorithm=hashes.SHA256(),
                        label=None
                    )
                )
                encrypted_group_key_b64 = base64.b64encode(encrypted_group_key).decode('utf-8')
                c.execute('''
                    INSERT OR REPLACE INTO group_member_keys (group_uuid, username, encrypted_group_key)
                    VALUES (?, ?, ?)
                ''', (group_uuid, new_member, encrypted_group_key_b64))

        conn.commit()
        conn.close()

# ============================================================================
# İSTEMCİ BAĞLANTISI (Her bağlantı için bir thread)
# ============================================================================
class ClientHandler(threading.Thread):
    def __init__(self, client_socket, address, server):
        threading.Thread.__init__(self, daemon=True)
        self.socket = client_socket
        self.address = address
        self.server = server
        self.username = None
        self.session_token = None
        self.running = True
        self.buffer = ""
        self.send_lock = threading.Lock()
        self._configure_socket()

    def _configure_socket(self):
        """Bağlantı kararlılığı için soket ayarları."""
        try:
            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
            self.socket.settimeout(60.0)
        except OSError:
            pass

    def run(self):
        print(f"[BAĞLANTI] {self.address} bağlandı.")
        try:
            while self.running:
                try:
                    data = self.socket.recv(65536)
                except socket.timeout:
                    continue
                except (ConnectionResetError, BrokenPipeError, OSError):
                    break

                if not data:
                    break

                self.buffer += data.decode('utf-8', errors='ignore')

                while '\n' in self.buffer:
                    line, self.buffer = self.buffer.split('\n', 1)
                    line = line.strip()
                    if not line:
                        continue

                    try:
                        message = json.loads(line)
                        if not isinstance(message, dict):
                            self.send_error("Mesaj JSON nesnesi olmalıdır.")
                            continue
                        self.handle_message(message)
                    except json.JSONDecodeError:
                        print(f"[HATA] Geçersiz JSON: {line[:100]}...")
                        self.send_error("Geçersiz mesaj formatı.")
        except ConnectionResetError:
            print(f"[BAĞLANTI] {self.address} bağlantıyı kopardı.")
        except Exception as e:
            if self.running:
                print(f"[HATA] {self.address}: {e}")
        finally:
            self.close()

    def send(self, data):
        """JSON mesajı gönder."""
        if not self.running:
            return False
        try:
            # Normalize outgoing packets so the client always receives a JSON object.
            try:
                if isinstance(data, (bytes, bytearray)):
                    text = data.decode('utf-8', errors='ignore')
                    parsed = json.loads(text)
                    data = parsed if isinstance(parsed, dict) else {'message': text}
                elif isinstance(data, str):
                    try:
                        parsed = json.loads(data)
                        data = parsed if isinstance(parsed, dict) else {'message': data}
                    except json.JSONDecodeError:
                        data = {'message': data}
                elif not isinstance(data, dict):
                    data = {'payload': data}

                if not isinstance(data, dict):
                    data = {'message': str(data)}
                if 'type' not in data:
                    data = {'type': 'protocol_packet', 'payload': data}
            except Exception as e:
                print(f"[SERVER] Paket normalizasyon hatası: {e}")
                data = {'type': 'protocol_error', 'message': str(data)}

            json_str = json.dumps(data, ensure_ascii=False) + '\n'
            with self.send_lock:
                self.socket.sendall(json_str.encode('utf-8'))
            return True
        except (BrokenPipeError, ConnectionResetError, OSError) as e:
            print(f"[GÖNDERİM HATASI] {self.address}: {e}")
            self.running = False
            return False
        except Exception as e:
            print(f"[GÖNDERİM HATASI] {self.address}: {e}")
            return False

    def send_error(self, message):
        """Hata mesajı gönder."""
        self.send({'type': 'error', 'message': message})

    def handle_message(self, msg):
        """Gelen mesajı işle."""
        msg_type = msg.get('type')

        # Session doğrulaması gerektiren işlemler
        if msg_type not in ('register', 'login', 'ping', 'get_public_key'):   # get_public_key için izin ver
            if not self.username:
                self.send_error("Önce giriş yapmalısınız.")
                return
            # Token doğrulaması (isteğe bağlı, burada basitlik için atlanabilir)
            # token = msg.get('session_token')
            # if not self.server.db.validate_session(self.username, token):
            #     self.send_error("Oturum süresi doldu, tekrar giriş yapın.")
            #     self.close()
            #     return

        # Mesaj tipine göre işle
        handlers = {
            'register': self.handle_register,
            'login': self.handle_login,
            'message': self.handle_private_message,
            'group_message': self.handle_group_message,
            'get_users': self.handle_get_users,
            'search_users': self.handle_search_users,
            'create_group': self.handle_create_group,
            'get_groups': self.handle_get_groups,
            'get_history': self.handle_get_history,
            'reaction': self.handle_reaction,
            'status_update': self.handle_status_update,
            'typing': self.handle_typing,
            'add_group_member': self.handle_add_group_member,
            'get_group_key': self.handle_get_group_key,
            'ping': self.handle_ping,
            'get_public_key': self.handle_get_public_key,   # YENİ
        }

        handler = handlers.get(msg_type)
        if handler:
            handler(msg)
        else:
            print(f"[UYARI] Bilinmeyen mesaj tipi: {msg_type}")

    # ----- İşleyiciler -----
    def handle_register(self, msg):
        """Yeni kullanıcı kaydı."""
        username = msg.get('username')
        password = msg.get('password')
        public_key = msg.get('public_key')

        if not username or not password:
            self.send_error("Kullanıcı adı ve şifre gerekli.")
            return

        if len(username) < 3 or len(username) > 20:
            self.send_error("Kullanıcı adı 3-20 karakter arasında olmalı.")
            return

        if len(password) < 6:
            self.send_error("Şifre en az 6 karakter olmalı.")
            return

        success = self.server.db.register_user(username, password, public_key)
        if success:
            self.send({'type': 'register_success', 'message': 'Kayıt başarılı! Şimdi giriş yapabilirsiniz.'})
            print(f"[KAYIT] Yeni kullanıcı: {username}")
        else:
            self.send_error("Bu kullanıcı adı zaten alınmış.")

    def handle_login(self, msg):
        """Kullanıcı girişi."""
        username = msg.get('username')
        password = msg.get('password')
        public_key = msg.get('public_key')  # İstemci her girişte public key gönderebilir, güncellenebilir.

        if not username or not password:
            self.send_error("Kullanıcı adı ve şifre gerekli.")
            return

        # Kimlik doğrulama
        if not self.server.db.authenticate_user(username, password):
            self.send_error("Kullanıcı adı veya şifre hatalı.")
            return

        # Public key'i güncelle (istemci yeni key üretmiş olabilir)
        if public_key:
            conn = self.server.db.get_connection()
            c = conn.cursor()
            c.execute("UPDATE users SET public_key = ? WHERE username = ?", (public_key, username))
            conn.commit()
            conn.close()

        # Aynı kullanıcının eski oturumunu kapat
        existing = self.server.get_online_user(username)
        if existing and existing is not self:
            existing.send({'type': 'admin_kick', 'message': 'Başka bir cihazdan giriş yapıldı.'})
            existing.close()

        # Session token oluştur
        token = self.server.db.set_session_token(username)

        # Kullanıcıyı kaydet
        self.username = username
        self.session_token = token

        # Çevrimiçi listesine ekle
        self.server.add_online_user(username, self)

        # Çevrimiçi kullanıcı listesini al
        online_users = self.server.db.get_online_users()

        # Başarılı giriş yanıtı
        self.send({
            'type': 'login_success',
            'username': username,
            'session_token': token,
            'online_users': online_users
        })

        print(f"[GİRİŞ] {username} giriş yaptı.")

        # Diğer kullanıcılara online durumu bildir
        self.server.broadcast_online_status(username, True)

    def handle_private_message(self, msg):
        """Özel mesaj gönderme."""
        receiver = msg.get('receiver')
        content = msg.get('content')
        encrypted_key = msg.get('encrypted_key')
        message_uuid = msg.get('message_uuid') or self.generate_uuid()
        message_type = msg.get('message_type', 'text')
        reply_to = msg.get('reply_to')
        timestamp = msg.get('timestamp', time.time())

        if not receiver or not content:
            self.send_error("Alıcı ve mesaj içeriği gerekli.")
            return

        # Alıcı var mı?
        conn = self.server.db.get_connection()
        c = conn.cursor()
        c.execute("SELECT username FROM users WHERE username = ?", (receiver,))
        if not c.fetchone():
            conn.close()
            self.send_error("Alıcı bulunamadı.")
            return
        conn.close()

        # Mesajı veritabanına kaydet
        msg_data = {
            'message_uuid': message_uuid,
            'sender': self.username,
            'receiver': receiver,
            'content': content,
            'encrypted_key': encrypted_key,
            'message_type': message_type,
            'reply_to': reply_to,
            'status': 'sent',
            'timestamp': timestamp
        }
        self.server.db.save_message(msg_data)

        # Alıcı çevrimiçi ise mesajı ilet
        receiver_handler = self.server.get_online_user(receiver)
        if receiver_handler:
            # Alıcıya mesajı gönder
            forward_msg = {
                'type': 'message',
                'message_uuid': message_uuid,
                'sender': self.username,
                'receiver': receiver,
                'content': content,
                'encrypted_key': encrypted_key,
                'message_type': message_type,
                'reply_to': reply_to,
                'timestamp': timestamp,
                'status': 'delivered'
            }
            receiver_handler.send(forward_msg)

            # Durumu delivered olarak güncelle
            self.server.db.update_message_status(message_uuid, 'delivered')
        else:
            # Alıcı çevrimdışı, mesaj depolandı
            pass

        # Göndericiye onay gönder (isteğe bağlı)
        self.send({
            'type': 'message_status',
            'message_uuid': message_uuid,
            'status': 'delivered' if receiver_handler else 'sent'
        })

    def handle_group_message(self, msg):
        """Grup mesajı gönderme."""
        group_uuid = msg.get('group_uuid')
        content = msg.get('content')
        encrypted_key = msg.get('encrypted_key')
        message_uuid = msg.get('message_uuid') or self.generate_uuid()
        message_type = msg.get('message_type', 'text')
        reply_to = msg.get('reply_to')
        timestamp = msg.get('timestamp', time.time())

        if not group_uuid or not content:
            self.send_error("Grup UUID ve mesaj içeriği gerekli.")
            return

        # Kullanıcı grubun üyesi mi?
        if not self.server.db.is_group_member(group_uuid, self.username):
            self.send_error("Bu gruba üye değilsiniz.")
            return

        # Mesajı kaydet
        msg_data = {
            'message_uuid': message_uuid,
            'sender': self.username,
            'group_uuid': group_uuid,
            'content': content,
            'encrypted_key': encrypted_key,
            'message_type': message_type,
            'reply_to': reply_to,
            'status': 'sent',
            'timestamp': timestamp
        }
        self.server.db.save_message(msg_data)

        # Grubun tüm üyelerine ilet (gönderen hariç)
        conn = self.server.db.get_connection()
        c = conn.cursor()
        c.execute("SELECT username FROM group_members WHERE group_uuid = ?", (group_uuid,))
        members = c.fetchall()
        conn.close()

        forward_msg = {
            'type': 'group_message',
            'message_uuid': message_uuid,
            'sender': self.username,
            'group_uuid': group_uuid,
            'content': content,
            'encrypted_key': encrypted_key,
            'message_type': message_type,
            'reply_to': reply_to,
            'timestamp': timestamp
        }

        for (member,) in members:
            if member != self.username:
                handler = self.server.get_online_user(member)
                if handler:
                    handler.send(forward_msg)

    def handle_get_users(self, msg):
        """Kullanıcı listesini gönder."""
        users = self.server.db.get_all_users()
        self.send({'type': 'user_list', 'users': users})

    def handle_search_users(self, msg):
        """Kullanıcı araması yap."""
        query = msg.get('query', '')
        if len(query) < 1:
            self.send_error("Arama sorgusu en az 1 karakter olmalı.")
            return

        results = self.server.db.search_users(query)
        self.send({'type': 'search_results', 'results': results})

    def handle_create_group(self, msg):
        """Yeni grup oluştur."""
        group_name = msg.get('group_name')
        description = msg.get('description', '')
        members = msg.get('members', [])

        if not group_name:
            self.send_error("Grup adı gerekli.")
            return

        # Benzersiz UUID oluştur
        group_uuid = self.generate_uuid()

        # Admin'i otomatik ekle (zaten members listesinde olabilir)
        if self.username not in members:
            members.append(self.username)

        # Veritabanına kaydet
        self.server.db.create_group(group_uuid, group_name, description, self.username, members)

        encrypted_group_key = self.server.db.get_group_key_for_user(group_uuid, self.username)
        # Başarılı yanıt
        self.send({
            'type': 'group_created',
            'group_uuid': group_uuid,
            'group_name': group_name,
            'description': description,
            'admin': self.username,
            'members': members,
            'encrypted_group_key': encrypted_group_key
        })

        print(f"[GRUP] {self.username} tarafından '{group_name}' grubu oluşturuldu.")

    def handle_get_groups(self, msg):
        """Kullanıcının üye olduğu grupları getir."""
        groups = self.server.db.get_user_groups(self.username)
        self.send({'type': 'user_groups', 'groups': groups})

    def handle_add_group_member(self, msg):
        """Gruba yeni üye ekle."""
        group_uuid = msg.get('group_uuid')
        new_member = msg.get('username')

        if not group_uuid or not new_member:
            self.send_error('Grup UUID ve kullanıcı adı gerekiyor.')
            return

        if not self.server.db.is_group_member(group_uuid, self.username):
            self.send_error('Bu gruba üye değilsiniz.')
            return

        group_info = next((g for g in self.server.db.get_user_groups(self.username) if g['group_uuid'] == group_uuid), None)
        if not group_info:
            self.send_error('Grup bulunamadı.')
            return

        self.server.db.add_group_member(group_uuid, new_member)
        self.send({'type': 'group_member_added', 'group_uuid': group_uuid, 'username': new_member})

        # Davet edilen kullanıcı çevrimiçi ise bilgilendir
        target = self.server.get_online_user(new_member)
        if target:
            encrypted_group_key = self.server.db.get_group_key_for_user(group_uuid, new_member)
            target.send({
                'type': 'group_invite',
                'group_uuid': group_uuid,
                'group_name': group_info['group_name'],
                'admin': group_info['admin'],
                'encrypted_group_key': encrypted_group_key
            })

    def handle_get_group_key(self, msg):
        """Kullanıcıya ait grup anahtarını gönder."""
        group_uuid = msg.get('group_uuid')
        if not group_uuid:
            self.send_error('Grup UUID gerekli.')
            return

        if not self.server.db.is_group_member(group_uuid, self.username):
            self.send_error('Bu gruba üye değilsiniz.')
            return

        encrypted_group_key = self.server.db.get_group_key_for_user(group_uuid, self.username)
        if not encrypted_group_key:
            self.send_error('Grup anahtarı bulunamadı.')
            return

        self.send({
            'type': 'group_key_response',
            'group_uuid': group_uuid,
            'encrypted_group_key': encrypted_group_key
        })

    def handle_get_history(self, msg):
        """Sohbet geçmişini getir."""
        other_user = msg.get('other_user')
        group_uuid = msg.get('group_uuid')
        limit = msg.get('limit', 100)

        if not other_user and not group_uuid:
            self.send_error("other_user veya group_uuid belirtilmelidir.")
            return

        messages = self.server.db.get_chat_history(self.username, other_user, group_uuid, limit)
        self.send({'type': 'chat_history', 'messages': messages})

    def handle_reaction(self, msg):
        """Mesaja reaksiyon ekle."""
        message_uuid = msg.get('message_uuid')
        reaction = msg.get('reaction')
        group_uuid = msg.get('group_uuid')

        if not message_uuid or not reaction:
            self.send_error("message_uuid ve reaction gerekli.")
            return

        # Reaksiyonu kaydet
        self.server.db.add_reaction(message_uuid, self.username, reaction)

        # Reaksiyonu ilgili kişilere bildir (özel mesaj veya grup)
        # Önce mesajı bul
        conn = self.server.db.get_connection()
        c = conn.cursor()
        if group_uuid:
            c.execute('''
                SELECT sender_username, receiver_username, group_uuid FROM messages
                WHERE message_uuid = ? AND group_uuid = ?
            ''', (message_uuid, group_uuid))
        else:
            c.execute('''
                SELECT sender_username, receiver_username FROM messages
                WHERE message_uuid = ?
            ''', (message_uuid,))
        row = c.fetchone()
        conn.close()

        if not row:
            return

        # Bildirimi gönder
        reaction_msg = {
            'type': 'reaction',
            'message_uuid': message_uuid,
            'username': self.username,
            'reaction': reaction,
            'group_uuid': group_uuid
        }

        if group_uuid:
            # Gruba üye olan herkese gönder (gönderen dahil)
            conn = self.server.db.get_connection()
            c = conn.cursor()
            c.execute("SELECT username FROM group_members WHERE group_uuid = ?", (group_uuid,))
            members = c.fetchall()
            conn.close()
            for (member,) in members:
                handler = self.server.get_online_user(member)
                if handler:
                    handler.send(reaction_msg)
        else:
            # Özel mesaj: gönderen ve alıcıya gönder
            sender, receiver = row[0], row[1]
            for user in (sender, receiver):
                if user != self.username:  # Kendine tekrar göndermeye gerek yok
                    handler = self.server.get_online_user(user)
                    if handler:
                        handler.send(reaction_msg)

    def handle_status_update(self, msg):
        """Mesaj durumu güncellemesi (okundu)."""
        message_uuid = msg.get('message_uuid')
        status = msg.get('status')  # 'read'
        group_uuid = msg.get('group_uuid')

        if not message_uuid or not status:
            return

        # Durumu güncelle
        self.server.db.update_message_status(message_uuid, status)

        # Durumu karşı tarafa bildir
        # Önce mesaj sahibini bul
        conn = self.server.db.get_connection()
        c = conn.cursor()
        if group_uuid:
            c.execute('''
                SELECT sender_username FROM messages
                WHERE message_uuid = ? AND group_uuid = ?
            ''', (message_uuid, group_uuid))
        else:
            c.execute('''
                SELECT sender_username FROM messages WHERE message_uuid = ?
            ''', (message_uuid,))
        row = c.fetchone()
        conn.close()

        if row:
            sender = row[0]
            if sender != self.username:
                handler = self.server.get_online_user(sender)
                if handler:
                    handler.send({
                        'type': 'message_status',
                        'message_uuid': message_uuid,
                        'status': status
                    })

    def handle_typing(self, msg):
        """Yazıyor bildirimi."""
        receiver = msg.get('receiver')
        is_typing = msg.get('is_typing', False)

        if not receiver:
            return

        # Alıcıya ilet
        receiver_handler = self.server.get_online_user(receiver)
        if receiver_handler:
            receiver_handler.send({
                'type': 'typing',
                'sender': self.username,
                'is_typing': is_typing
            })

    def handle_ping(self, msg):
        """Ping isteğine pong yanıtı ver."""
        self.send({'type': 'pong', 'timestamp': msg.get('timestamp')})
        # Son görülmeyi güncelle
        if self.username:
            self.server.db.update_last_seen(self.username)

    def handle_get_public_key(self, msg):
        """Bir kullanıcının public key'ini getir."""
        target = msg.get('username')
        if not target:
            self.send_error("Kullanıcı adı belirtilmedi.")
            return
        public_key = self.server.db.get_user_public_key(target)
        if public_key:
            self.send({'type': 'public_key_response', 'username': target, 'public_key': public_key})
        else:
            self.send_error(f"{target} kullanıcısının public key'i bulunamadı.")

    def generate_uuid(self):
        """Basit bir UUID üreteci."""
        import uuid
        return str(uuid.uuid4())

    def close(self):
        """Bağlantıyı kapat ve kaynakları temizle."""
        if not self.running:
            return
        self.running = False
        username = self.username
        if username:
            self.server.remove_online_user(username)
            self.server.db.set_online(username, False)
            self.server.broadcast_online_status(username, False)
            print(f"[ÇIKIŞ] {username} ({self.address}) ayrıldı.")
        try:
            self.socket.shutdown(socket.SHUT_RDWR)
        except OSError:
            pass
        try:
            self.socket.close()
        except OSError:
            pass

# ============================================================================
# ANA SUNUCU SINIFI
# ============================================================================
class ChatServer:
    def __init__(self, host=SERVER_IP, port=SERVER_PORT):
        self.host = host
        self.port = port
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.running = False
        self.db = DatabaseManager()

        # Çevrimiçi kullanıcılar: username -> ClientHandler
        self.online_users = {}
        self.lock = threading.Lock()

    def start(self):
        """Sunucuyu başlat."""
        try:
            self.socket.bind((self.host, self.port))
            self.socket.listen(10)
            self.running = True
            print(f"{Colors.OKGREEN}[SUNUCU] {self.host}:{self.port} adresinde dinleniyor...{Colors.ENDC}")

            # Admin konsol thread'i (opsiyonel)
            admin_thread = threading.Thread(target=self.admin_console, daemon=True)
            admin_thread.start()

            self.socket.settimeout(1.0)
            while self.running:
                try:
                    client_socket, address = self.socket.accept()
                except socket.timeout:
                    continue
                except OSError:
                    break
                handler = ClientHandler(client_socket, address, self)
                handler.start()

        except KeyboardInterrupt:
            print("\n[SUNUCU] Kapatılıyor...")
        except Exception as e:
            print(f"[HATA] {e}")
        finally:
            self.shutdown()

    def add_online_user(self, username, handler):
        """Kullanıcıyı çevrimiçi listesine ekle."""
        with self.lock:
            self.online_users[username] = handler
        self.db.set_online(username, True)

    def remove_online_user(self, username):
        """Kullanıcıyı çevrimiçi listesinden çıkar."""
        with self.lock:
            if username in self.online_users:
                del self.online_users[username]
        self.db.set_online(username, False)

    def get_online_user(self, username):
        """Kullanıcının handler'ını döndür (çevrimiçi ise)."""
        with self.lock:
            return self.online_users.get(username)

    def get_online_users(self):
        """Tüm çevrimiçi kullanıcı adlarını döndür."""
        with self.lock:
            return list(self.online_users.keys())

    def broadcast_online_status(self, username, online):
        """Tüm çevrimiçi kullanıcılara bir kullanıcının durum değişikliğini bildir."""
        status_msg = {
            'type': 'user_list',
            'users': [{'username': u, 'online': True} for u in self.get_online_users()]
        }
        with self.lock:
            for handler in self.online_users.values():
                try:
                    handler.send(status_msg)
                except:
                    pass

    def broadcast_admin_message(self, message):
        """Tüm kullanıcılara yönetici mesajı gönder."""
        msg = {'type': 'admin_message', 'message': message}
        with self.lock:
            for handler in self.online_users.values():
                try:
                    handler.send(msg)
                except:
                    pass

    def kick_user(self, username):
        """Bir kullanıcıyı sunucudan at."""
        handler = self.get_online_user(username)
        if handler:
            handler.send({'type': 'admin_kick', 'message': 'Yönetici tarafından atıldınız.'})
            handler.close()

    def shutdown(self):
        """Sunucuyu kapat."""
        self.running = False
        # Tüm istemcilere kapanma mesajı gönder
        with self.lock:
            for handler in self.online_users.values():
                try:
                    handler.send({'type': 'server_shutdown', 'message': 'Sunucu kapatılıyor...'})
                except:
                    pass
                handler.close()
        self.socket.close()
        print("[SUNUCU] Kapatıldı.")

    def admin_console(self):
        """Basit bir yönetici konsolu."""
        while self.running:
            try:
                cmd = input(f"{Colors.BOLD}admin> {Colors.ENDC}").strip()
                if cmd == "quit" or cmd == "exit":
                    self.running = False
                    break
                elif cmd == "users":
                    users = self.get_online_users()
                    print(f"Çevrimiçi kullanıcılar ({len(users)}): {', '.join(users)}")
                elif cmd.startswith("broadcast "):
                    message = cmd[10:]
                    self.broadcast_admin_message(message)
                    print(f"Duyuru gönderildi: {message}")
                elif cmd.startswith("kick "):
                    username = cmd[5:]
                    self.kick_user(username)
                    print(f"{username} atıldı.")
                elif cmd == "help":
                    print("Komutlar: users, broadcast <mesaj>, kick <kullanıcı>, quit")
                else:
                    print("Bilinmeyen komut. 'help' yazın.")
            except EOFError:
                break
            except Exception as e:
                print(f"Konsol hatası: {e}")

# ============================================================================
# ANA ÇALIŞTIRMA
# ============================================================================
if __name__ == "__main__":
    print("""
    ╔══════════════════════════════════════════╗
    ║     Secure Chat Server v2.0              ║
    ║     Oracle Cloud'da çalıştırılmak üzere  ║
    ╚══════════════════════════════════════════╝
    """)

    # Port komut satırından alınabilir
    port = SERVER_PORT
    if len(sys.argv) > 1:
        try:
            port = int(sys.argv[1])
        except ValueError:
            print("Geçersiz port numarası, varsayılan kullanılıyor.")

    server = ChatServer(host=SERVER_IP, port=port)
    try:
        server.start()
    except KeyboardInterrupt:
        print("\n[SUNUCU] Kapatılıyor...")
        server.shutdown()
