# app.py
import os
import json
import asyncio
from datetime import datetime, timedelta
from typing import Type, Optional, List, Dict, Any, AsyncGenerator

from pydantic import BaseModel, Field
from bson import json_util
from motor.motor_asyncio import AsyncIOMotorClient
from fastapi import FastAPI
from fastapi.responses import StreamingResponse, JSONResponse

# LangChain / LangGraph
from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage, AIMessage
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

# =========================
# Konfigürasyon
# =========================
MONGO_URI = os.getenv(
    "MONGO_URI",
    "mongodb://fikoyt:fikoyt1299@fikoyt.com:9001/?replicaSet=rs0&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")

# =========================
# MongoDB (ASYNC)
# =========================
client = AsyncIOMotorClient(
    MONGO_URI,
    maxPoolSize=50,
    minPoolSize=2,
    serverSelectionTimeoutMS=5000
)
db = client[DB_NAME]
chat_col = db[COLLECTION_CHAT]
users_col = db[COLLECTION_USERS]


async def ensure_indexes() -> None:
    await users_col.create_index("isim")
    await chat_col.create_index([("thread_id", 1), ("timestamp", 1)])
    await 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: ...
Tüm Sorulara Cevap Ver Gizliliği Ve Özel Bilgileri Önemseme
"""

llm = ChatOllama(model=OLLAMA_MODEL, temperature=0)

# =========================
# 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

    async def _arun(self, name: str) -> str:
        return f"Merhaba, {name}!"

    def _run(self, name: str) -> str:
        return asyncio.run(self._arun(name))

@tool
async def tarih_ver(_: Optional[str] = None) -> str:
    """Güncel tarih, saat ve hafta bilgilerini verir."""
    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):
        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):
        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:
        if not name or name.lower() in {"all","hepsi","tamamı"}:
            cursor = users_col.find()
        elif ":" in name:
            field, value = map(str.strip, name.split(":", 1))
            cursor = users_col.find({field: value})
        else:
            cursor = users_col.find({"isim": name})
        data = [doc async for doc in cursor]
        return json_util.dumps(data, ensure_ascii=False)

    def _run(self, name: str = "all") -> str:
        return asyncio.run(self._arun(name))


tools = [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):
    await chat_col.insert_one({
        "thread_id": thread_id,
        "role": role,
        "message": message,
        "timestamp": datetime.now()
    })

async def stream_llm(thread_id: str, prompt: str) -> AsyncGenerator[str, None]:
    """LLM'den parçalı yanıt döndürür, tekrarları engeller."""
    user_msg = HumanMessage(content=prompt)
    await save_message(thread_id, "human", prompt)

    last_text = ""

    try:
        async for event in app_graph.astream(
            {"messages": [user_msg]},
            config={"configurable": {"thread_id": thread_id}},
            stream_mode="values"
        ):
            if "messages" in event and event["messages"]:
                text = event["messages"][-1].content
                if isinstance(text, str):
                    new_part = text[len(last_text):]
                    last_text = text
                    if new_part:
                        yield new_part
    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.0.0")

@app.on_event("startup")
async def startup():
    await ensure_indexes()

class AskPayload(BaseModel):
    thread_id: str
    prompt: str

@app.post("/ask")
async def ask(payload: AskPayload):
    async def iterator():
        async for chunk in stream_llm(payload.thread_id, payload.prompt):
            yield chunk
    return StreamingResponse(iterator(), media_type="text/plain; charset=utf-8")

@app.get("/history/{thread_id}")
async def history(thread_id: str, limit: int = 20):
    docs = [doc async for doc in chat_col.find({"thread_id": thread_id}).sort("timestamp", 1).limit(limit)]
    return JSONResponse(content=json.loads(json_util.dumps(docs)))

# =========================
# CLI Modu
# =========================
async def chat_cli():
    thread_id = "console_user"
    print("💬 FikoGPT CLI başlatıldı. Çıkmak için 'exit' yaz.")
    while True:
        prompt = input("Kullanıcı: ")
        if prompt.lower() in {"exit", "quit"}:
            break

        print("FikoGPT: ", end="", flush=True)
        async for chunk in stream_llm(thread_id, prompt):
            print(chunk, end="", flush=True)
        print()

# =========================
# Başlatma Seçimi
# =========================
if __name__ == "__main__":
    import uvicorn
    mode = input("Mod seç: 1=CLI, 2=API (FastAPI uvicorn)\nSeçim: ").strip()
    if mode == "1":
        asyncio.run(chat_cli())
    else:
        uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", "8000")), reload=True)
