"""تحليل لقطات الكاميرات بنموذج رؤية — مزايا النوع vlm.

المزوّد قابل للتبديل عبر VEEDON_VLM_PROVIDER:
  claude   → Claude Vision (جودة أعلى، سحابي، يحتاج ANTHROPIC_API_KEY)          [افتراضي]
  ollama   → نموذج رؤية محلي عبر Ollama (خصوصية كاملة — الصور لا تغادر المنشأة)
  lmstudio → نموذج رؤية محلي عبر LM Studio (نفس الفكرة)

Ollama وLM Studio يوفّران واجهة متوافقة مع OpenAI، فيتشاركان مساراً واحداً.
يرجع dict: {"flagged": bool, "score": float, "note": str}

الرؤية المحلية (خصوصية PDPL): شغّل نموذجاً يتبع التعليمات ويفهم الفيديو —
**SmolVLM2-Video** (صغير) أو **Qwen2.5-VL** — عبر vLLM/LM Studio داخل المنشأة.
دليل الإعداد: docs/LOCAL-VLM.md.
"""

import base64
import json
import os

PROVIDER = os.environ.get("VEEDON_VLM_PROVIDER", "claude").lower()

# نموذج افتراضي لكل مزوّد (يُتجاوز بـ VEEDON_VLM_MODEL)
_DEFAULT_MODEL = {
    "claude": "claude-opus-4-8",
    "ollama": "llama3.2-vision",
    "lmstudio": "local-model",
}
MODEL = os.environ.get("VEEDON_VLM_MODEL", _DEFAULT_MODEL.get(PROVIDER, "claude-opus-4-8"))

# عنوان الخادم المحلي المتوافق مع OpenAI (Ollama :11434، LM Studio :1234)
_DEFAULT_URL = {"ollama": "http://localhost:11434/v1",
                "lmstudio": "http://localhost:1234/v1"}
OPENAI_BASE_URL = os.environ.get("VEEDON_VLM_URL", _DEFAULT_URL.get(PROVIDER, ""))

PROMPTS = {
    "table_cleanliness": (
        "أنت مفتش جودة في مطعم. انظر إلى الصورة وقيّم نظافة الطاولات ومحطات العمل الظاهرة: "
        "بقايا طعام، أسطح متسخة، فوضى، أدوات متراكمة."
    ),
    "poultry_health": (
        "أنت طبيب بيطري متخصص في الدواجن. انظر إلى صورة العنبر وقيّم المؤشرات الظاهرة: "
        "طيور خاملة أو راقدة بشكل غير طبيعي، نفوق ظاهر، تجمع غير معتاد، تباعد عن العلف والماء."
    ),
    "fight_detection": (
        "أنت مشرف سلامة مدرسي. هل في الصورة عنف جسدي بين طلاب: دفع، ضرب، تشابك، "
        "أو تجمّع مفاجئ يحيط بشخصين؟ flagged=true فقط عند وجود مؤشر عنف واضح."
    ),
    "bullying": (
        "أنت مشرف سلوك طلابي. هل في الصورة مؤشرات تنمّر: استهداف جماعي لطالب منفرد، "
        "عزل/إقصاء، وضعيات تهديد أو مضايقة، طالب منكمش أو محاصَر؟ لا تُبلّغ عن تفاعل طبيعي."
    ),
    "stair_fall": (
        "أنت نظام سلامة يراقب سلالم/درج مدرسة. هل يوجد شخص ساقط أو متعثّر أو ممدّد "
        "على الدرج بشكل غير طبيعي؟ flagged=true عند أي وضعية سقوط أو استلقاء خطِر."
    ),
    "student_sleeping": (
        "أنت مساعد صفّي. هل يوجد طالب نائم أو واضع رأسه على الطاولة أو مستلقٍ أثناء "
        "الحصة؟ اذكر عدد الطلاب النائمين تقريبياً في الملاحظة."
    ),
    "ai_deterrence": (
        "أنت نظام ردع أمني. صِف الشخص في منطقة مراقبة (ملابسه ولونها وموقعه بإيجاز) "
        "وصُغ في الملاحظة **رسالة تحذير صوتية عربية مهذّبة ومتصاعدة** تُذاع عبر المكبّر: "
        "«أنت في منطقة مراقبة، يُرجى المغادرة». flagged=true عند وجود شخص متسكّع/غير مصرّح."
    ),
}

DEFAULT_PROMPT = "قيّم الصورة من منظور التزام تشغيلي وأشر إلى أي مشكلة ظاهرة."

INSTRUCTIONS = (
    "\nأجب بـ JSON فقط بهذا الشكل بالضبط:\n"
    '{"flagged": true/false, "score": 0-10, "note": "ملاحظة عربية موجزة"}\n'
    "flagged=true إذا كانت هناك مشكلة تستحق تنبيه المدير. score شدة المشكلة (0 لا مشكلة)."
)


def _parse(text: str) -> dict:
    """يستخرج JSON الحكم من رد النموذج. النماذج القوية (Claude/Qwen-VL) تُعيد JSON
    فنأخذ حكمها؛ النماذج الصغيرة الواصفة (moondream) تُعيد وصفاً حراً — نُبقيه كملاحظة
    إعلامية بلا تنبيه (لا إنذار كاذب)، تظهر للمراجعة البشرية."""
    start, end = text.find("{"), text.rfind("}")
    if start != -1 and end > start:
        try:
            data = json.loads(text[start:end + 1])
            return {"flagged": bool(data.get("flagged")),
                    "score": float(data.get("score", 0)),
                    "note": str(data.get("note", ""))[:1000]}
        except (json.JSONDecodeError, ValueError):
            pass
    return {"flagged": False, "score": 0.0, "note": text.strip()[:1000]}


def _claude(prompt: str, b64s: list) -> dict:
    import anthropic
    client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
    content = [{"type": "image", "source": {"type": "base64",
                "media_type": "image/jpeg", "data": b}} for b in b64s]
    content.append({"type": "text", "text": prompt})
    msg = client.messages.create(
        model=MODEL, max_tokens=512,
        messages=[{"role": "user", "content": content}])
    return _parse("".join(b.text for b in msg.content if b.type == "text"))


def _openai_compatible(prompt: str, b64s: list) -> dict:
    """Ollama / LM Studio / نموذج مدرب — واجهة OpenAI موحّدة على السيرفر السحابي."""
    import httpx
    content = [{"type": "text", "text": prompt}]
    content += [{"type": "image_url",
                 "image_url": {"url": f"data:image/jpeg;base64,{b}"}} for b in b64s]
    r = httpx.post(
        f"{OPENAI_BASE_URL.rstrip('/')}/chat/completions",
        json={"model": MODEL, "max_tokens": 512, "temperature": 0,
              "messages": [{"role": "user", "content": content}]},
        timeout=180)
    r.raise_for_status()
    return _parse(r.json()["choices"][0]["message"]["content"])


def analyze_frames(feature_key: str, frames: list, prompt_override: str = "") -> dict:
    """كشف سلوكي على السيرفر السحابي: يحلّل **لقطات متتالية** (سياق زمني للحركة —
    عراك/سقوط/سرقة) عبر المزوّد المُهيّأ (claude/ollama/lmstudio/نموذج مدرب).
    prompt_override: نص كشف يكتبه الأدمن (custom_vlm — «اكشف أي شيء تصفه بالكلمات»)."""
    if PROVIDER == "claude" and not os.environ.get("ANTHROPIC_API_KEY"):
        return {"flagged": False, "score": 0.0,
                "note": "تحليل AI غير مفعّل (ANTHROPIC_API_KEY غير موجود)"}
    if PROVIDER in ("ollama", "lmstudio") and not OPENAI_BASE_URL:
        return {"flagged": False, "score": 0.0,
                "note": f"تحليل AI غير مفعّل (VEEDON_VLM_URL غير محدّد لـ {PROVIDER})"}
    if not frames:
        return {"flagged": False, "score": 0.0, "note": "لا لقطات"}
    n = len(frames)
    seq = (f"\nهذه {n} لقطات متتالية بفارق ثوانٍ لنفس الكاميرا — حلّل السلوك والحركة عبرها."
           if n > 1 else "")
    base = prompt_override.strip() or PROMPTS.get(feature_key, DEFAULT_PROMPT)
    prompt = base + seq + INSTRUCTIONS
    b64s = [base64.standard_b64encode(f).decode() for f in frames]
    call = _claude if PROVIDER == "claude" else _openai_compatible
    try:
        return call(prompt, b64s)
    except Exception as exc:
        # نموذج أحادي الصورة (moondream) يرفض عدة صور → أعد المحاولة بآخر لقطة.
        # النماذج متعددة الصور (Qwen2-VL/Qwen2.5-VL) تحلّل التسلسل كاملاً.
        if len(b64s) > 1:
            try:
                return call(prompt, b64s[-1:])
            except Exception as exc2:
                exc = exc2
        return {"flagged": False, "score": 0.0,
                "note": f"تعذر تحليل AI ({PROVIDER}): {exc}"}


def analyze(feature_key: str, image_bytes: bytes) -> dict:
    return analyze_frames(feature_key, [image_bytes])  # لقطة واحدة — توافق خلفي
