import os
import json
import asyncio
from datetime import datetime, timedelta
from typing import Type, Optional, List, Dict, Any, AsyncGenerator
import sys
import platform
from bs4 import BeautifulSoup
import requests
from pydantic import PrivateAttr
try:
    if platform.system() != "Windows":
        #import uvloop
        asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
except ImportError:
    pass

from pydantic import BaseModel, Field, ValidationError
from bson import json_util
from motor.motor_asyncio import AsyncIOMotorClient
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, JSONResponse

# LangChain / LangGraph
from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from langchain.tools import BaseTool, tool
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, MessagesState

"""
Bu dosya, önceki sürümün ayni işlevselliğini korurken; gecikmeyi, bellek
kullanımını ve hataya dayanıklılığı iyileştirmek üzere optimize edilmiştir.
Başlıca farklar:
- uvloop ile hızlı event loop
- Motor/Mongo bağlantısı ve indexler için güvenli başlatma
- Araçlarda (Tool) sadece async yolun kullanımı; sync _run kaldırıldı
- Streaming fark üretimi (diff) daha güvenli ve düşük CPU maliyetli
- LangChain Ollama istemcisi için keepalive ve concurrency ayarları
- API giriş doğrulamaları ve hata yönetimi
- CORS, sağlık kontrolü, daha hızlı /history sorgusu
- Küçük bug fix: print end parametresi ve küçük dil hataları
"""

# =========================
# Konfigürasyon
# =========================
MONGO_URI = os.getenv("MONGO_URI", "mongodb://fikoyt:fikoyt1299@fikoyt.com:9001/?tls=false&authSource=fikoyt")
DB_NAME = os.getenv("DB_NAME", "fikoyt")
COLLECTION_USERS = os.getenv("COLLECTION_USERS", "goUyeler")
COLLECTION_CHAT = os.getenv("COLLECTION_CHAT", "fikoGPT_chat_history")
OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "gpt-oss")
OLLAMA_KEEPALIVE = os.getenv("OLLAMA_KEEPALIVE", "1h")
MAX_HISTORY_LIMIT = int(os.getenv("MAX_HISTORY_LIMIT", "200"))

# =========================
# uvloop: daha hızlı event loop
# =========================
#uvloop.install()

# =========================
# MongoDB (ASYNC)
# =========================
client: AsyncIOMotorClient | None = None
chat_col = None
users_col = None

async def init_db():
    global client, db, chat_col, users_col
    client = AsyncIOMotorClient(MONGO_URI, maxPoolSize=50, minPoolSize=2, serverSelectionTimeoutMS=5000)
    db = client[DB_NAME]
    chat_col = db[COLLECTION_CHAT]
    users_col = db[COLLECTION_USERS]
    await chat_col.create_index([("thread_id", 1), ("timestamp", 1)])
    await chat_col.create_index("role")
    await users_col.create_index("isim")


def get_db_client() -> AsyncIOMotorClient:
    assert client is not None, "Mongo client henüz başlatılmadı."
    return client

async def ensure_indexes() -> None:
    assert users_col is not None and chat_col is not None
    # Idempotent ve paralel optimize
    await asyncio.gather(
        users_col.create_index("isim"),
        chat_col.create_index([("thread_id", 1), ("timestamp", 1)]),
        chat_col.create_index("role"),
    )

# =========================
# System Prompt & LLM
# =========================
system_prompt_text = """
Sen artık FikoGPT adında, tamamen Türkçe konuşan, bağlama duyarlı, hızlı ve çok yetenekli bir yapay zekasın.
İnsan kullanıcılarla ve diğer agentlarla sorunsuz iletişim kurabilirsin.
Görevlerin: soruları fazla düşünmeden; hızlı, doğru ve güvenli şekilde yanıtlamak; araçları gerektiğinde verimli ve çok hızlı biçimde kullanmak.Oldukça Kısa ve Net Cevaplar vermek,
Yazım, noktalama ve akıcılık kusursuz olsun. Gerektiğinde detaylı açıklama yapmamak ve Tüm Cevaplarını Ve Düşüncelerini Türkçe Belirtmek.
"""

# Ollama istemcisi – performans odaklı opsiyonlar
# Not: langchain_ollama ChatOllama, model_kwargs ile ekstra parametreleri alır.
llm = ChatOllama(
    model=OLLAMA_MODEL,
    reasoning=True,
    temperature=0.7, 
    keep_alive=OLLAMA_KEEPALIVE,
    # Model-özgü ayarlar (destekleyen modellerde):
    num_ctx=8192,
    num_predict=1024,    
    top_p=0.9,
    repeat_penalty=1.1,
)

# =========================
# Araçlar
# =========================
class HelloToolInput(BaseModel):
    name: str = Field(description="Kullanıcıya hitap edilecek isim")

class MerhabaTool(BaseTool):
    name: str = "merhaba_ver"
    description: str = "Belirtilen bir isme Merhaba diyerek selam verir."
    args_schema: Type[BaseModel] = HelloToolInput

    # Sadece async yol kullanılsın (LangGraph ile tam uyumlu)
    async def _arun(self, name: str) -> str:  # type: ignore[override]
        return f"Merhaba, {name}!"

    def _run(self, name: str = "all") -> str:
        # sync çağrı geldiğinde async sürümü çalıştır   
        
        return asyncio.run(self._arun(name))
    


class MongoChatHistoryToolInput(BaseModel):
    thread_id: str = Field(description="Hangi konuşma/oturum ID'sinin geçmişi çekilecek")
    limit: int = Field(default=10, description="Kaç mesaj getirileceği (max 200)")

class MongoChatHistoryTool(BaseTool):
    name: str = "mongodb_sohbet_gecmisi"
    description: str = (
        "MongoDB'den belirtilen thread_id için eski sohbet geçmişini getirir. "
        "En yeni mesajlar yukarıda olacak şekilde sıralı döner."
    )
    args_schema: Type[BaseModel] = MongoChatHistoryToolInput

    async def _arun(self, thread_id: str, limit: int = 10) -> str:  # type: ignore[override]
        assert chat_col is not None
        limit = min(max(limit, 1), MAX_HISTORY_LIMIT)
        cursor = (
            chat_col.find({"thread_id": thread_id}, projection={"_id": 0})
            .sort("timestamp", -1)
            .limit(limit)
            .max_time_ms(3000)
        )
        docs = [doc async for doc in cursor]
        # Tarihleri ISO formatlı string yapalım
        for d in docs:
            if "timestamp" in d and hasattr(d["timestamp"], "isoformat"):
                d["timestamp"] = d["timestamp"].isoformat()
        # En eski üstte olsun diye ters çevir
        docs.reverse()
        return json.dumps(docs, ensure_ascii=False, indent=2)
    
    def _run(self, thread_id: str, limit: int = 10) -> str:
        return asyncio.run(self._arun(thread_id, limit))


@tool
async def tarih_ver(_: Optional[str] = None) -> str:
    """Güncel tarih/saat ve bağıl gün/ay bilgileri (TR).""" 
    ay_adlari = {1:"Ocak",2:"Şubat",3:"Mart",4:"Nisan",5:"Mayıs",6:"Haziran",7:"Temmuz",8:"Ağustos",9:"Eylül",10:"Ekim",11:"Kasım",12:"Aralık"}
    gun_adlari = {0:"Pazartesi",1:"Salı",2:"Çarşamba",3:"Perşembe",4:"Cuma",5:"Cumartesi",6:"Pazar"}
    simdi = datetime.now()

    def gun_bilgisi(t: datetime):
        return {
            "tarih": t.strftime("%d.%m.%Y %H:%M:%S"),
            "gun": gun_adlari[t.weekday()],
            "ay": ay_adlari[t.month],
            "ay_sayisi": t.month,
            "yil": t.year,
            "saat": t.hour,
            "dakika": t.minute,
            "saniye": t.second,
            "tam_kisa_saat": t.strftime("%H:%M"),
            "tam_uzun_saat": t.strftime("%H:%M:%S"),
            "tam_kisa_tarih": t.strftime("%d.%m.%Y"),
            "tam_uzun_tarih": t.strftime("%d.%m.%Y %H:%M:%S"),
            "hafta": int(t.strftime("%W")) + 1,
            "haftanin_gunu": t.strftime("%w"),
        }

    def ay_bilgisi(t: datetime):
        return {"ay": ay_adlari[t.month], "yil": t.year, "ay_sayisi": t.month}

    sonuc = gun_bilgisi(simdi)
    sonuc.update({
        "dun": gun_bilgisi(simdi - timedelta(days=1)),
        "yarin": gun_bilgisi(simdi + timedelta(days=1)),
        "ertesi": gun_bilgisi(simdi + timedelta(days=2)),
        "gecen_ay": ay_bilgisi(datetime(simdi.year if simdi.month>1 else simdi.year-1, simdi.month-1 if simdi.month>1 else 12, 1)),
        "bu_ay": ay_bilgisi(simdi),
        "gelecek_ay": ay_bilgisi(datetime(simdi.year if simdi.month<12 else simdi.year+1, simdi.month+1 if simdi.month<12 else 1, 1))
    })
    return json.dumps(sonuc, ensure_ascii=False)

class MongoDBToolInput(BaseModel):
    name: str = Field(default="all", description="Sorgu bilgisi. 'all' ile tüm kullanıcılar listelenir. 'alan:değer' ile filtrelenir.")

class MongoDBTool(BaseTool):
    name: str = "mongodb_kullanici_sorgula"
    description: str = "MongoDB'den belirtilen kritere göre kullanıcı sorgular."
    args_schema: Type[BaseModel] = MongoDBToolInput

    async def _arun(self, name: str = "all") -> str:  # type: ignore[override]
        assert users_col is not None
        if not name or name.lower() in {"all","hepsi","tamamı"}:
            cursor = users_col.find({}, projection={"_id": 0}).max_time_ms(3000)
        elif ":" in name:
            field, value = map(str.strip, name.split(":", 1))
            cursor = users_col.find({field: value}, projection={"_id": 0}).max_time_ms(3000)
        else:
            cursor = users_col.find({"isim": name}, projection={"_id": 0}).max_time_ms(3000)
        data = [doc async for doc in cursor.limit(1000)]  # kötü durumlarda hafızayı koru
        return json_util.dumps(data, ensure_ascii=False)
    
    def _run(self, name: str = "all") -> str:
        # sync çağrı geldiğinde async sürümü çalıştır
        return asyncio.run(self._arun(name))
    
#--------------------------------------------------------------------------
# =========================
# Araçlar
# =========================
class WebAraToolInput(BaseModel):
    konu: str

class WebAraTool(BaseTool):
    name: str = "web_ara"
    description: str = "Verilen konu ile ilgili web araması yapar ve linkleri toplar."
    args_schema: type[BaseModel] = WebAraToolInput

    async def _arun(self, *, konu: str) -> list[dict]:
        import requests
        from bs4 import BeautifulSoup
        try:
            url = f"https://www.google.com/search?q={konu}"
            headers = {"User-Agent": "Mozilla/5.0"}
            res = requests.get(url, headers=headers, timeout=5)
            soup = BeautifulSoup(res.text, "html.parser")

            results = []
            for item in soup.select("div.BNeawe.vvjwJb.AP7Wnd")[:5]:
                parent = item.find_parent("a")
                link = parent["href"] if parent else ""
                results.append({"title": item.get_text(), "link": link})

            if not results:
                return [{"title": "Sonuç bulunamadı", "link": ""}]
            return results
        except Exception as e:
            return [{"title": f"Hata: {e}", "link": ""}]

    def _run(self, *, konu: str) -> list[dict]:
        return asyncio.run(self._arun(konu=konu))

# =========================
# Temel araçlarla agent oluşturulacak
# =========================
basic_tools = [
    WebAraTool(),
    MongoChatHistoryTool(),
    MongoDBTool(),
    MerhabaTool(),
    tarih_ver
]

react_agent = create_react_agent(model=llm, tools=basic_tools, prompt=system_prompt_text)

# =========================
# Agent’ı gerektiren araçlar
# =========================
class SiteIncelemeToolInput(BaseModel):
    url: str

class SiteIncelemeTool(BaseTool):
    name: str = "site_incele_ve_ozetle"
    description: str = "Verilen URL'deki sayfa içeriğini çeker ve özetler."
    args_schema: type[BaseModel] = SiteIncelemeToolInput

    # agent için PrivateAttr kullanıyoruz
    _agent: Any = PrivateAttr()

    def __init__(self, agent):
        super().__init__()
        self._agent = agent

    async def _arun(self, *, url: str) -> str:
        import requests
        from bs4 import BeautifulSoup
        from langchain_core.messages import HumanMessage
        try:
            res = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=5)
            soup = BeautifulSoup(res.text, "html.parser")

            metin = " ".join([p.get_text() for p in soup.find_all("p")])
            if not metin:
                return "Sayfa içeriği bulunamadı."

            prompt = f"Aşağıdaki metni kısa ve anlaşılır şekilde özetle:\n{metin}"
            user_msg = HumanMessage(content=prompt)

            async for event in self._agent.astream(
                {"messages": [user_msg]},
                config={},
                stream_mode="last"
            ):
                return event["messages"][-1].content
        except Exception as e:
            return f"Site inceleme sırasında hata oluştu: {e}"

    def _run(self, *, url: str) -> str:
        return asyncio.run(self._arun(url=url))

# =========================
# Web arama ve özetleme aracı
# =========================
class WebAraVeOzetToolInput(BaseModel):
    konu: str


async def web_arama_ve_ozetle(konu: str, agent) -> list[dict]:
    arama_tool = WebAraTool()
    linkler = await arama_tool._arun(konu=konu)

    inceleme_tool = SiteIncelemeTool(agent=agent)
    for item in linkler:
        if item["link"]:
            summary = await inceleme_tool._arun(url=item["link"])
            item["summary"] = summary
        else:
            item["summary"] = "Link yok."
    return linkler

class WebAraVeOzetTool(BaseTool):
    name: str = "web_ara_ve_ozetle"
    description: str = "Konuya dair web araması yapar, linkleri ve özetleri getirir."
    args_schema: type[BaseModel] = WebAraToolInput

    # agent için PrivateAttr
    _agent: Any = PrivateAttr()

    def __init__(self, agent):
        super().__init__()
        self._agent = agent

    async def _arun(self, *, konu: str) -> list[dict]:
        # web_arama_ve_ozetle fonksiyonunu çağırırken self._agent kullan
        return await web_arama_ve_ozetle(konu, agent=self._agent)

    def _run(self, *, konu: str) -> list[dict]:
        return asyncio.run(self._arun(konu=konu))

# =========================
# Tüm araçlar listesi
# =========================
site_inceleme_tool = SiteIncelemeTool(agent=react_agent)
web_ozet_tool = WebAraVeOzetTool(agent=react_agent)

tools = basic_tools + [site_inceleme_tool, web_ozet_tool]

# Araçlar listesi (yalnızca async tool'lar)
# tools = [WebAraTool(),   SiteIncelemeTool(agent=react_agent),
#     WebAraVeOzetTool(agent=react_agent),MongoChatHistoryTool(),MongoDBTool(), MerhabaTool(), tarih_ver]

# =========================
# Agent Kurulumu
# =========================
memory = MemorySaver()
react_agent = create_react_agent(model=llm, tools=tools, prompt=system_prompt_text)
# =========================

# =========================
graph = StateGraph(MessagesState)
graph.add_node("agent", react_agent)
graph.set_entry_point("agent")
app_graph = graph.compile(checkpointer=memory)

# =========================
# Yardımcı Fonksiyonlar
# =========================
async def save_message(thread_id: str, role: str, message: str) -> None:
    assert chat_col is not None
    await chat_col.insert_one({
        "thread_id": thread_id,
        "role": role,
        "message": message,
        "timestamp": datetime.now(),
    })

# =========================
# AI durum takibi ile streaming
# =========================
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
from typing import AsyncGenerator

app = FastAPI()

import asyncio
from typing import AsyncGenerator

# Varsayılan olarak kullanılacak
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage

# async def stream_llm(thread_id: str, prompt: str) -> AsyncGenerator[str, None]:
#     try:
#         await save_message(thread_id, "human", prompt)
#         user_msg = HumanMessage(content=prompt)

#         last_ai_message = ""
#         animation_frames = ["⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"]
#         anim_index = 0

#         def get_next_animation_frame():
#             nonlocal anim_index
#             frame = animation_frames[anim_index]
#             anim_index = (anim_index + 1) % len(animation_frames)
#             return frame

#         async for event in app_graph.astream_events(
#             {"messages": [user_msg]},
#             config={"configurable": {"thread_id": thread_id}},
#             version="v1"  # Varsa eski sürümler için belirtilebilir.
#         ):
#             kind = event["event"]
#             data = event["data"]

#             if kind == "on_tool_end":
#                 yield f"[🛠️ Araç Sonucu] {data.get('output', '')}\n"

#             elif kind == "on_llm_stream":
#                 if "chunk" in data:
#                     chunk = data["chunk"]
#                     if isinstance(chunk, AIMessage):
#                         if not chunk.content.strip():
#                             yield f"{get_next_animation_frame()} Düşünüyor...\r"
#                             await asyncio.sleep(0.1)
#                         else:
#                             # Parçalı metinleri birleştirip anlık gönder
#                             yield chunk.content
#                             last_ai_message += chunk.content
                            
#     except Exception as e:
#         yield f"[❌ Hata] Beklenmeyen bir sorun oluştu: {e}\n"
#     finally:
#         # Son mesajı kaydet
#         if last_ai_message:
#             await save_message(thread_id, "ai", last_ai_message)
#         yield "\n[✅ İşlem Tamamlandı]\n"


import asyncio
from typing import AsyncGenerator

from langchain_core.messages import HumanMessage, AIMessage, ToolMessage

# app_graph ve save_message fonksiyonlarınızın dışarıdan geldiğini varsayıyorum.
# from your_module import app_graph, save_message

import asyncio
from typing import AsyncGenerator

from langchain_core.messages import HumanMessage, AIMessage, ToolMessage

# app_graph ve save_message fonksiyonlarınızın dışarıdan geldiğini varsayıyorum.
# from your_module import app_graph, save_message

async def stream_llm(thread_id: str, prompt: str) -> AsyncGenerator[str, None]:
    """
    Her prompt için:
    - Önceki tool mesajları gösterilmez
    - AI cevabı tek seferde
    - Düşünüyor animasyonu
    """
    await save_message(thread_id, "human", prompt)
    user_msg = HumanMessage(content=prompt)

    last_text = ""
    animation = ["⠁","⠂","⠄","⡀","⢀","⠠","⠐","⠈"]
    anim_index = 0

    # Bu prompt için görülen tool mesajları
    seen_tool_names = set()

    try:
        async for event in app_graph.astream(
            {"messages": [user_msg], "reset_context": True},  # Temiz context
            config={"configurable": {"thread_id": thread_id}},durability="async",
            stream_mode=["values", "updates"]
        ):
            mode, data = event
            messages = []

            if mode == "values":
                messages = data.get("messages", [])
            elif mode == "updates":
                messages = data.get("agent", {}).get("messages", [])

            if not messages:
                # Düşünüyor animasyonu
                yield f"{animation[anim_index]} Düşünüyor...\r"
                anim_index = (anim_index + 1) % len(animation)
                await asyncio.sleep(0.1)
                continue

            for msg in messages:
                # Tool mesajlarını tekilleştir
                if isinstance(msg, ToolMessage):
                    if msg.name in seen_tool_names:
                        continue
                    seen_tool_names.add(msg.name)
                    yield f"[🛠️ Araç Kullanılıyor] {msg.name}\n"
                    break

                # AI mesajlarını sakla
                elif isinstance(msg, AIMessage):
                    if msg.content.strip():
                        last_text = msg.content
                    else:
                        # Boşsa animasyon göster
                        yield f"{animation[anim_index]} Düşünüyor...\r"
                        anim_index = (anim_index + 1) % len(animation)
                        await asyncio.sleep(0.1)

    except Exception as e:
        yield f"[❌ Beklenmeyen hata: {e}]\n"

    finally:
        if last_text.strip():
            await save_message(thread_id, "ai", last_text)
            yield f"\n[🧠 FikoGpt Cevabı] {last_text}\n"
        else:
            yield "\n[🧠 FikoGpt Cevabı] (cevap üretilmedi)\n"
        yield "[✅ Tamamlandı]\n"
        seen_tool_names.clear()




async def stream_llmdddddddddddddddd(thread_id: str, prompt: str) -> AsyncGenerator[str, None]:
    """
    CLI için animasyonlu, akıcı LLM streaming.
    AI düşünürken animasyon, araç çıktısı ayrı gösterim.
    """
    await save_message(thread_id, "human", prompt)
    user_msg = HumanMessage(content=prompt)

    last_text = ""
    prev_text = ""

    status_prefix = {
        "tool_start": "[🔧 Araç başlıyor] ",
        "tool_end": "[📤 Araç sonucu] ",
        "chain_start": "[🔄 Zincir başladı] ",
        "chain_end": "[🔄 Zincir bitti] ",
        "llm_start": "[🤖 Model düşünmeye başladı] ",
        "llm_end": "[✅ Model tamamlandı] ",
    }

    # Animasyon için
    animation = ["⠁","⠂","⠄","⡀","⢀","⠠","⠐","⠈"]
    anim_index = 0
    show_anim = True

    try:
        async for event in app_graph.astream(
            {"messages": [user_msg]},
            config={"configurable": {"thread_id": thread_id}},
            stream_mode=["values", "updates"],
        ):
            mode, data = event

            # Mesajları al
            if mode == "values":
                messages = data.get("messages", [])
            elif mode == "updates":
                messages = data.get("agent", {}).get("messages", [])
            else:
                print(f"Unknown mode: {event}")
                messages = []

            if not messages:
                continue

            content = messages[-1].content
            if not isinstance(content, str):
                continue

            # Diff
            common = 0
            for a, b in zip(prev_text, content):
                if a != b:
                    break
                common += 1
            new_part = content[common:]
            if new_part:
                yield new_part
                prev_text = content
            last_text = content

            # Status prefix
            if "_status" in data:
                yield f"\n{status_prefix.get(data['_status'], '')}{data['_status']}\n"

            # Tool mesajları
            if mode == "updates":
                tools = [m for m in messages if isinstance(m, ToolMessage)]
                for t in tools:
                    yield f"\n[🛠️ Araç Çıktısı] {t.content}\n"

            # Animasyon
            if show_anim:
                sys.stdout.write(f"\r{animation[anim_index]} Düşünüyor...")
                sys.stdout.flush()
                anim_index = (anim_index + 1) % len(animation)
                await asyncio.sleep(0.1)

    except Exception as e:
        yield f"\n[❌ Beklenmeyen hata: {e}]\n"

    finally:
        # Animasyon satırını temizle
        sys.stdout.write("\r" + " " * 30 + "\r")
        sys.stdout.flush()

        if last_text:
            await save_message(thread_id, "ai", last_text)
        yield "\n[✅ Tamamlandı]\n"

async def stream_llm11(thread_id: str, prompt: str) -> AsyncGenerator[str, None]:
    user_msg = HumanMessage(content=prompt)
    await save_message(thread_id, "human", prompt)

    prev = ""
    last_text: Optional[str] = None
    try:
        async for event in app_graph.astream(
            {"messages": [user_msg]},
            config={"configurable": {"thread_id": thread_id}},
            stream_mode="values",
        ):
            if not event or "messages" not in event:
                continue
            content = event["messages"][ -1 ].content  # type: ignore[index]
            if not isinstance(content, str):
                continue
            last_text = content
            # Güvenli diff: ortak prefix uzunluğunu bulup sadece yeniyi gönder
            # (CPU ve network tasarrufu)
            common = 0
            for a, b in zip(prev, content):
                if a != b:
                    break
                common += 1
            new_part = content[common:]
            if new_part:
                yield new_part
                prev = content
    except Exception as e:
        yield f"\n[❌ Hata: {e}]\n"
    finally:
        if last_text:
            await save_message(thread_id, "ai", last_text)

# =========================
# FastAPI Uygulaması
# =========================
app = FastAPI(title="FikoGPT Turbo", version="1.1.0")

# CORS – gerekli ise domainlerinizi kısıtlayın
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.on_event("startup")
async def startup() -> None:
    global client, chat_col, users_col
    # Havuz ayarları optimize
    client = AsyncIOMotorClient(
        MONGO_URI,
        maxPoolSize=100,
        minPoolSize=4,
        serverSelectionTimeoutMS=10000,
        tls=False,
        uuidRepresentation='standard',
        tz_aware=True,
        retryWrites=True,
    )
    db = client[DB_NAME]
    chat_col = db[COLLECTION_CHAT]
    users_col = db[COLLECTION_USERS]
    await ensure_indexes()

@app.on_event("shutdown")
async def shutdown() -> None:
    global client
    if client is not None:
        client.close()
        client = None

class AskPayload(BaseModel):
    thread_id: str
    prompt: str

@app.post("/ask")
async def ask(payload: AskPayload):
    if not payload.thread_id or not payload.prompt:
        raise HTTPException(status_code=400, detail="thread_id ve prompt zorunludur")

    async def iterator():
        async for chunk in stream_llm(payload.thread_id, payload.prompt):
            # Küçük bekleme ile flush (yüksek QPS'de CPU'yu rahatlatır)
            yield chunk
            await asyncio.sleep(0)  # kontrolü event loop'a bırak

    return StreamingResponse(iterator(), media_type="text/plain; charset=utf-8")

@app.get("/history/{thread_id}")
async def history(thread_id: str, limit: int = 20):
    if limit < 1:
        limit = 1
    limit = min(limit, MAX_HISTORY_LIMIT)
    assert chat_col is not None
    cursor = (
        chat_col
        .find({"thread_id": thread_id}, projection={"_id": 0})
        .sort("timestamp", 1)
        .limit(limit)
        .max_time_ms(3000)
    )
    docs = [doc async for doc in cursor]
    return JSONResponse(content=json.loads(json_util.dumps(docs)))

@app.get("/health")
async def health():
    # Basit sağlik kontrolü
    try:
        db_client = get_db_client()
        await db_client.admin.command("ping")
        mongo_ok = True
    except Exception:
        mongo_ok = False
    return {"status": "ok", "mongo": mongo_ok}

# =========================
# CLI Modu
# =========================
async def chat_cli():
    
    thread_id = "console_user"
    print("💬 FikoGPT CLI başlatıldı. Çıkmak için 'exit' yaz.")
    while True:
        try:
            prompt = input("Kullanıcı: ")
        except (EOFError, KeyboardInterrupt):
            break

        if prompt.strip().lower() in {"exit", "quit"}:
            break

        print("FikoGPT: ", end="", flush=True)
        async for chunk in stream_llm(thread_id, prompt):
            # Satır sonunu bozmadan akıt
            end_char = "" if (len(chunk) and chunk[-1] not in {"\n", "\r"}) else ""
            print(chunk, end=end_char, flush=True)
        print()

# =========================
# Başlatma Seçimi
# =========================
if __name__ == "__main__":
    import uvicorn

    mode = os.getenv("MODE")
    if not mode:
        try:
            mode = input("Mod seç: 1=CLI, 2=API (FastAPI uvicorn)\nSeçim: ").strip()
        except (EOFError, KeyboardInterrupt):
            mode = "2"

    if mode == "1":
        # CLI modunda da uvloop aktif
        async def main():
                    await init_db()       # MongoDB bağlantısı
                    await chat_cli()      # CLI başlat
        asyncio.run(main())
    else:
        # Uvicorn performans önerisi: --workers >= CPU çekirdeği, http=httptools, loop=uvloop
        uvicorn.run(
            "FikoGpt.fikoGptTurbo:app",
            host="0.0.0.0",
            port=int(os.getenv("PORT", "8000")),
            reload=True,
            loop="asyncio",
            http="httptools",
            backlog=2048,
            timeout_keep_alive=20,
            log_level=os.getenv("LOG_LEVEL", "info"),
        )
