import base64
import json
import os
from typing import Optional

from dotenv import load_dotenv
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_openai import ChatOpenAI

load_dotenv()

# ============================================================================
# SABIT TIBBİ ANALİZ PROMPTU
# ============================================================================

MEDICAL_ANALYSIS_PROMPT = """Sen çoklu LLM uygulamasına (OpenAI, Grok, Gemini, Claude) entegre edilmiş gelişmiş bir tıbbi görüntüleme analiz asistanısın. 
Sana 9 adet BT (CT) görüntüsü gönderilecek. Bunların TAMAMINI inceleyip belirtilen 4 patoloji için tek bir sonuç döndüreceksin.

BAĞLAM:
- Sana 9 adet toraks BT görüntüsü gönderildi.
- Bu 9 görüntü AYNI VAKAYA/HASTAYA ait farklı kesitlerdir.
- Görüntülerin TAMAMINI inceleyerek belirlenen patolojileri tespit edeceksin.
- Sadece şu 4 patolojiyi arayacaksın: Pnömotoraks, Hemotoraks, Akciğer Kontüzyonu, Kot Kırığı

YANIT KURALLARI (ÇOK ÖNEMLİ):
1. Çıktı GEÇERLİ BIR JSON STRING olmalıdır. JSON'dan önce veya sonra açıklama EKLEME.
2. JSON'u Markdown, kod blokları veya backtick'lerle SARMA.
3. Yorum EKLEME.
4. Her patoloji için "var" veya "yok" belirt.
5. Eğer patoloji varsa, hangi görüntüde ve nerede olduğunu belirt.
6. Eğer patoloji yoksa, lokasyon için "-" yaz.
7. Tüm anahtarlar ve değerler Türkçe olmalı.

Kısaltmalar:
pnx = Pnomotoraks
hmx = Hemotoraks
akk = Akciğer Kontüzyonu
kot = Kot Kırığı

BEKLENİLEN JSON ŞEMASI:
{{    
  "llm": "{provider}",
  "pnx": {{
      "var_yok": "var",
      "lokasyon": "Sağ akciğer üst zonunda, görüntü 3 ve 4'te görülmektedir"
    }},
  "hmx": {{
      "var_yok": "yok",
      "lokasyon": "-"
    }},
  "akk": {{
      "var_yok": "var",
      "lokasyon": "Sol akciğer alt lobunda, görüntü 6-8 arasında yaygın kontüzyon alanları"
    }},
  "kot": {{
      "var_yok": "var",
      "lokasyon": "Sol 5. ve 6. kotlarda kırık hattı, görüntü 5'te net izlenmektedir"
    }}
}}

ALDIĞIN GİRDİLER:
- 9 adet toraks BT görüntüsü (aynı vakaya ait, farklı kesitler)
- Görüntüler sırayla numaralandırılmıştır (1-9)

GÖREV:
1. 9 görüntünün TAMAMINI dikkatlice incele.
2. Her görüntüyü akılda tutarak genel bir değerlendirme yap.
3. Sadece şu 4 patolojiyi ara:
   - Pnömotoraks (akciğer kollapsı, plevral boşlukta hava)
   - Hemotoraks (plevral boşlukta kan birikimi)
   - Akciğer Kontüzyonu (akciğer dokusunda ezilme, kanama)
   - Kot Kırığı (kaburga kemiği kırığı)
4. Her patoloji için "var" veya "yok" belirt.
5. Eğer varsa, hangi görüntülerde ve nerede olduğunu açıkla.
6. SADECE yukarıdaki şemaya uyan TEK BİR JSON string döndür.

EMİN DEĞİLSEN:
- Yine de geçerli JSON döndür.
- Eğer bir patolojiden emin değilsen "yok" olarak işaretle.
- Yapıyı koru.

ŞİMDİ SADECE JSON'U DÖNDÜR.
"""


# ============================================================================
# LLM SEÇİCİ FONKSİYON
# ============================================================================


def get_llm(provider_name: str):
    """
    Seçilen sağlayıcıya göre uygun LangChain LLM nesnesini döndürür.

    Args:
        provider_name: "openai", "grok", "gemini", "claude" değerlerinden biri

    Returns:
        LangChain ChatModel nesnesi

    Raises:
        ValueError: Desteklenmeyen sağlayıcı seçilirse
    """
    provider = provider_name.lower().strip()

    if provider == "openai":
        api_key = os.environ.get("OPENAI_API_KEY")
        if not api_key:
            raise ValueError("OPENAI_API_KEY ortam değişkeni ayarlanmamış!")
        return ChatOpenAI(
            model="gpt-5.1",  # Vision destekli model
            api_key=api_key,
            temperature=0,
        )

    elif provider == "grok":
        # Grok, OpenAI uyumlu API kullanıyor (xAI)
        api_key = os.environ.get("XAI_API_KEY")
        base_url = os.environ.get("XAI_BASE_URL", "https://api.x.ai/v1")
        if not api_key:
            raise ValueError("XAI_API_KEY ortam değişkeni ayarlanmamış!")
        return ChatOpenAI(
            model="grok-4-1-fast-reasoning",
            api_key=api_key,
            base_url=base_url,
            temperature=0,
        )

    elif provider == "gemini":
        api_key = os.environ.get("GOOGLE_API_KEY")
        if not api_key:
            raise ValueError("GOOGLE_API_KEY ortam değişkeni ayarlanmamış!")
        return ChatGoogleGenerativeAI(
            model="gemini-2.5-pro",  # Vision destekli model
            google_api_key=api_key,
            temperature=0,
        )

    elif provider == "claude":
        api_key = os.environ.get("ANTHROPIC_API_KEY")
        if not api_key:
            raise ValueError("ANTHROPIC_API_KEY ortam değişkeni ayarlanmamış!")
        chat = ChatAnthropic(
            model="claude-sonnet-4-5-20250929",  # Vision destekli model
            api_key=api_key,
            temperature=0,
        )
        return chat

    else:
        raise ValueError(
            f"Desteklenmeyen sağlayıcı: {provider_name}. "
            "Geçerli seçenekler: openai, grok, gemini, claude"
        )


# ============================================================================
# GÖRÜNTÜ ANALİZ FONKSİYONU
# ============================================================================


def encode_image_to_base64(image_path: str) -> str:
    """
    Görüntü dosyasını base64 string'e çevirir.

    Args:
        image_path: Görüntü dosyasının yolu

    Returns:
        Base64 encode edilmiş görüntü string'i
    """
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode("utf-8")


def detect_image_mime_type(image_path: str) -> str:
    """
    Görüntü dosya uzantısından MIME tipini tahmin eder.

    Args:
        image_path: Görüntü dosyasının yolu

    Returns:
        MIME tipi (örn: "image/jpeg")
    """
    extension = image_path.lower().split(".")[-1]
    mime_types = {
        "jpg": "image/jpeg",
        "jpeg": "image/jpeg",
        "png": "image/png",
        "gif": "image/gif",
        "bmp": "image/bmp",
        "webp": "image/webp",
    }
    return mime_types.get(extension, "image/jpeg")


def run_image_analysis(llm, prompt: str, image_paths: list[str], provider: str) -> str:
    """
    9 adet toraks BT görüntüsünün toplu analizini çalıştırır ve LLM'den JSON yanıt alır.
    Aynı vakaya ait görüntüleri analiz edip 4 belirli patoloji için tek sonuç döndürür.

    Args:
        llm: LangChain LLM nesnesi
        prompt: Analiz promptu (MEDICAL_ANALYSIS_PROMPT)
        image_paths: Analiz edilecek 9 BT görüntüsünün dosya yolları listesi

    Returns:
        LLM'den dönen yanıt string'i

    Raises:
        ValueError: Tam olarak 9 görüntü sağlanmazsa
    """
    # 9 görüntü kontrolü
    if len(image_paths) != 9:
        raise ValueError(
            f"Tam olarak 9 görüntü gerekli, {len(image_paths)} adet sağlandı!"
        )

    final_prompt = prompt.format(provider=provider)

    # Mesaj içeriğini hazırla - önce prompt metni
    content = [{"type": "text", "text": final_prompt}]

    # Her görüntüyü base64'e çevirip ekle
    for i, image_path in enumerate(image_paths, 1):
        print(f"{i}/9 are being processed: {image_path}")

        if not os.path.exists(image_path):
            raise FileNotFoundError(f"File not found: {image_path}")

        base64_image = encode_image_to_base64(image_path)
        mime_type = detect_image_mime_type(image_path)

        # Görüntü numarası etiketi
        content.append({"type": "text", "text": f"\n--- Görüntü {i} ---"})

        # Görüntü verisi
        content.append(
            {
                "type": "image_url",
                "image_url": {"url": f"data:{mime_type};base64,{base64_image}"},
            }
        )

    # LangChain mesajı oluştur
    message = HumanMessage(content=content)

    print("\n9 images are being sent to LLM...")

    # BURADA GERÇEK GÖRSEL ÇAĞRISI YAPILIR
    # LangChain invoke() metodu ile modele gönderim
    response = llm.invoke([message])

    # Yanıtı string olarak döndür
    return response.content


# ============================================================================
# JSON DOĞRULAMA VE PRETTY-PRINT
# ============================================================================


def validate_and_display_json(response_text: str, provider: str) -> None:
    """
    LLM yanıtını JSON olarak parse etmeye çalışır ve ekrana yazdırır.

    Args:
        response_text: LLM'den dönen yanıt string'i
    """
    # Markdown kod bloklarını temizle (bazı modeller ekleyebilir)
    cleaned_text = response_text.strip()
    if cleaned_text.startswith("```"):
        # İlk ve son satırı kaldır
        lines = cleaned_text.split("\n")
        cleaned_text = "\n".join(lines[1:-1]).strip()

    try:
        # JSON parse et
        json_data = json.loads(cleaned_text)
        return json_data

    except json.JSONDecodeError as e:
        data = {
            "llm": "{provider}",
            "pnx": {"var_yok": "yok", "lokasyon": "-"},
            "hmx": {"var_yok": "yok", "lokasyon": "-"},
            "akk": {"var_yok": "yok", "lokasyon": "-"},
            "kot": {"var_yok": "yok", "lokasyon": "-"},
        }
        return data


# ============================================================================
# MAIN FONKSİYON - KULLANICI AKIŞI
# ============================================================================


def ai_engine(provider, image_paths):
    provider = provider.lower().strip()

    try:
        print(provider)
        llm = get_llm(provider)
        print(f"{provider.upper()} LLM is used.\n")
    except ValueError as e:
        print(f"Hata: {e}")
        return

    # 9 görüntü kontrolü
    if len(image_paths) != 9:
        print(
            f"Error: Exactly 9 images must be sent. Received, {len(image_paths)} images!"
        )
        return

    # Dosya varlık kontrolü
    for i, path in enumerate(image_paths, 1):
        if not os.path.exists(path):
            print(f"ERROR: File not found ({i}): {path}")
            return

    # Analiz çalıştır
    try:
        response = run_image_analysis(
            llm=llm,
            prompt=MEDICAL_ANALYSIS_PROMPT,
            image_paths=image_paths,
            provider=provider,
        )

        # 5. Yanıtı doğrula ve göster
        return validate_and_display_json(response, provider)

    except Exception as e:
        print(f"ERROR while running image analysis: {e}")
        import traceback

        traceback.print_exc()
        return None
