"""Veedon Health Gateway — وحدة صحة الطلاب (تمايز فريد للمدارس).

يستقبل قياسات الأجهزة القابلة للارتداء (Apple HealthKit / Google Fit / CGM / Empatica)
عبر webhook، يطابقها بالطالب، يطبّق عتبات حسب الحالة المزمنة، ويُنبّه عيادة المدرسة
وولي الأمر عبر واتساب مع تحديد الفرع/الكاميرا الأقرب.

حدود صريحة: طبقة تنبيه وربط — ليست جهازاً طبياً ولا تشخّص.
"""

from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy import (Boolean, Column, DateTime, ForeignKey, Numeric, String,
                        Text, select, text)
from sqlalchemy.orm import Mapped, Session, mapped_column

from . import notifier
from .db import Base, get_db
from .models import Branch, Organization, utcnow
from datetime import datetime, timedelta
import base64
import hashlib
import hmac
import json
import os
import time
import uuid

router = APIRouter(prefix="/api/health", tags=["health"])


class Student(Base):
    __tablename__ = "students"
    id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
    organization_id: Mapped[uuid.UUID | None] = mapped_column(
        ForeignKey("organizations.id"), nullable=True)
    branch_id: Mapped[uuid.UUID | None] = mapped_column(
        ForeignKey("branches.id"), nullable=True)
    name: Mapped[str] = mapped_column(Text)
    condition: Mapped[str | None] = mapped_column(Text, nullable=True)
    guardian_phone: Mapped[str | None] = mapped_column(Text, nullable=True)
    clinic_phone: Mapped[str | None] = mapped_column(Text, nullable=True)
    wearable_id: Mapped[str | None] = mapped_column(String, unique=True, nullable=True)


class HealthAlert(Base):
    __tablename__ = "health_alerts"
    id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
    student_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("students.id"))
    alert_type: Mapped[str] = mapped_column(Text)
    value = Column(Numeric, nullable=True)
    severity: Mapped[str] = mapped_column(Text, default="high")
    note: Mapped[str] = mapped_column(Text, default="")
    detected_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), default=utcnow)
    notified: Mapped[bool] = mapped_column(Boolean, default=False)
    resolved: Mapped[bool] = mapped_column(Boolean, default=False)


# قواعد العتبات لكل قياس → (نوع التنبيه، رسالة) عند تجاوز الحد
def evaluate(metric: str, value: float, flag: bool) -> tuple[str, str] | None:
    metric = metric.lower()
    if metric == "seizure" and flag:
        return "seizure", "اشتباه نوبة صرع — تدخّل فوري"
    if metric == "glucose":
        if value < 70:
            return "glucose_low", f"هبوط سكر خطر ({value:.0f} mg/dL)"
        if value > 250:
            return "glucose_high", f"ارتفاع سكر خطر ({value:.0f} mg/dL)"
    if metric in ("hr", "heart_rate"):
        if value > 150:
            return "hr_high", f"تسارع نبض غير طبيعي ({value:.0f} bpm)"
        if value < 40:
            return "hr_low", f"تباطؤ نبض غير طبيعي ({value:.0f} bpm)"
    if metric in ("spo2", "oxygen") and value < 92:
        return "spo2_low", f"انخفاض أكسجين الدم ({value:.0f}%)"
    return None


def _process(db: Session, student: "Student", metric: str, value: float,
             flag: bool) -> dict | None:
    """قيّم قياساً واحداً؛ عند تجاوز العتبة أنشئ تنبيهاً وأبلغ العيادة/ولي الأمر.

    يعيد {alert_type, notified} إن رُفع تنبيه، أو None إن كان القياس طبيعياً.
    """
    verdict = evaluate(metric, value, flag)
    if not verdict:
        return None
    alert_type, note = verdict

    # نافذة تهدئة: لا نكرّر نفس نوع التنبيه لنفس الطالب خلالها (تفادي الإغراق)
    cooldown_s = _int_setting(db, "ow_cooldown_s", 300)
    if cooldown_s > 0:
        since = utcnow() - timedelta(seconds=cooldown_s)
        dup = db.scalar(select(HealthAlert.id).where(
            HealthAlert.student_id == student.id,
            HealthAlert.alert_type == alert_type,
            HealthAlert.detected_at >= since))
        if dup:
            return {"alert_type": alert_type, "notified": False, "suppressed": True}

    alert = HealthAlert(student_id=student.id, alert_type=alert_type,
                        value=value, note=note)
    db.add(alert)
    db.commit()

    branch = db.get(Branch, student.branch_id) if student.branch_id else None
    camera_hint = ""
    if branch and branch.cameras:
        camera_hint = f"\nآخر موقع معروف: كاميرا {branch.cameras[0].name}"
    org = db.get(Organization, student.organization_id) if student.organization_id else None
    msg = "\n".join([
        "🚑 تنبيه صحي — Veedon.ai",
        f"الطالب: {student.name}"
        + (f" ({student.condition})" if student.condition else ""),
        f"المدرسة: {org.name if org else '—'}"
        + (f" / {branch.name}" if branch else ""),
        f"الحالة: {note}",
        f"الوقت: {utcnow().strftime('%Y-%m-%d %H:%M')}" + camera_hint,
    ])
    numbers = ",".join(n for n in [student.clinic_phone, student.guardian_phone] if n)
    sent = notifier.send_alert(numbers, msg) if numbers else 0
    if sent:
        alert.notified = True
        db.commit()
    return {"alert_type": alert_type, "notified": bool(sent)}


@router.post("/alert")
def ingest_wearable(payload: dict, db: Session = Depends(get_db)):
    """webhook مباشر من الساعة/الجهاز: {wearable_id, metric, value?, flag?}."""
    wid = (payload.get("wearable_id") or "").strip()
    student = db.scalar(select(Student).where(Student.wearable_id == wid)) if wid else None
    if not student:
        raise HTTPException(404, "unknown wearable_id")
    result = _process(db, student, payload.get("metric", ""),
                      float(payload.get("value", 0)), bool(payload.get("flag")))
    return {"status": "alerted", **result} if result else {"status": "ok", "alert": None}


# ── محوّل Open Wearables ──────────────────────────────────────────────
# اربط الطالب بوضع Open Wearables user_id في حقل wearable_id بشاشة «صحة الطلاب».
# إعدادات الربط يغيّرها مدير النظام من «الإعدادات ← API Secrets» (جدول system_settings)؛
# متغيّر البيئة VEEDON_OW_WEBHOOK_SECRET يبقى احتياطياً إن لم تُضبط في اللوحة.
OW_WEBHOOK_SECRET = os.getenv("VEEDON_OW_WEBHOOK_SECRET", "")


def get_system_setting(db: Session, key: str, default=None):
    """يقرأ قيمة من جدول system_settings المشترك (لوحة Beyond Senses).

    setting_value مخزّن كـ JSON {"value": ...}. يتحمّل غياب الجدول (SQLite للتطوير)
    فيعيد default — فلا يتعطّل الويب-هوك محلياً قبل تهيئة اللوحة.
    """
    try:
        raw = db.execute(
            text("SELECT setting_value FROM system_settings WHERE setting_key = :k"),
            {"k": key},
        ).scalar()
    except Exception:
        db.rollback()
        return default
    if raw is None:
        return default
    if isinstance(raw, str):
        try:
            raw = json.loads(raw)
        except (ValueError, TypeError):
            return raw or default
    if isinstance(raw, dict):
        return raw.get("value", default)
    return raw


def _int_setting(db: Session, key: str, default: int) -> int:
    v = get_system_setting(db, key, default)
    try:
        return int(v)
    except (TypeError, ValueError):
        return default

# series_type من Open Wearables → أسماء القياسات التي يفهمها evaluate()
SERIES_MAP = {
    "heart_rate": "hr",
    "oxygen_saturation": "spo2",
    "spo2": "spo2",
    "blood_glucose": "glucose",
    "glucose": "glucose",
}


def verify_svix(secret: str, headers, raw_body: str) -> bool:
    """يتحقق من توقيع Svix (يستخدمه Open Wearables) — بلا اعتماديات، stdlib فقط.

    المحتوى الموقّع: "{svix-id}.{svix-timestamp}.{raw_body}" → HMAC-SHA256 → base64،
    ويُقارن بإحدى قيم v1 في ترويسة svix-signature. يرفض الطوابع الأقدم من 5 دقائق.
    السرّ بصيغة whsec_<base64>.
    """
    msg_id = headers.get("svix-id")
    ts = headers.get("svix-timestamp")
    sig_header = headers.get("svix-signature", "")
    if not (msg_id and ts and sig_header):
        return False
    try:
        if abs(time.time() - int(ts)) > 300:  # تحمّل 5 دقائق (منع إعادة الإرسال)
            return False
    except (TypeError, ValueError):
        return False
    key = secret.split("_", 1)[1] if secret.startswith("whsec_") else secret
    try:
        key_bytes = base64.b64decode(key)
    except Exception:
        return False
    signed = f"{msg_id}.{ts}.{raw_body}".encode()
    expected = base64.b64encode(
        hmac.new(key_bytes, signed, hashlib.sha256).digest()).decode()
    for part in sig_header.split():  # قد تحمل عدّة تواقيع "v1,<b64> v1,<b64>"
        val = part.split(",", 1)[1] if "," in part else part
        if hmac.compare_digest(val, expected):
            return True
    return False


def _check_secret(db: Session, request: Request, raw: bytes) -> bool:
    """يقبل النداء إن: (أ) لا سرّ مضبوط، أو (ب) توقيع Svix صحيح (سرّ whsec_)، أو
    (ج) سرّ مشترك مطابق في X-Webhook-Secret/Bearer (للمصادر البسيطة وزر الاختبار)."""
    secret = get_system_setting(db, "ow_webhook_secret") or OW_WEBHOOK_SECRET
    if not secret:
        return True
    if "svix-signature" in request.headers:
        return verify_svix(secret, request.headers, raw.decode("utf-8", "replace"))
    got = (request.headers.get("x-webhook-secret")
           or request.headers.get("authorization", "").removeprefix("Bearer ").strip())
    return bool(got) and hmac.compare_digest(got, secret)


@router.post("/ow/webhook")
async def open_wearables_webhook(request: Request, db: Session = Depends(get_db)):
    """يحوّل webhook من Open Wearables (موقّع Svix) إلى منطق العتبات نفسه.

    الحمولة: {"type":"heart_rate.created","data":{"user_id","series_type",
    "samples":[{"value","unit","is_daily_total",...}]}} — راجع docs/WEARABLES-INTEGRATION.md.
    """
    # مفتاح الإيقاف: يعطّله مدير النظام من اللوحة (ow_enabled=false)
    if get_system_setting(db, "ow_enabled", True) is False:
        raise HTTPException(403, "Open Wearables integration disabled")

    raw = await request.body()  # الجسم الخام لازم للتحقق من توقيع Svix
    if not _check_secret(db, request, raw):
        raise HTTPException(401, "bad webhook signature/secret")
    try:
        payload = json.loads(raw or b"{}")
    except (ValueError, TypeError):
        raise HTTPException(400, "invalid JSON body")

    data = payload.get("data") or {}
    metric = SERIES_MAP.get(str(data.get("series_type") or "").lower())
    if not metric:
        return {"status": "ignored", "reason": "unmapped series_type"}
    user_id = str(data.get("user_id") or "").strip()
    student = (db.scalar(select(Student).where(Student.wearable_id == user_id))
               if user_id else None)
    if not student:
        raise HTTPException(404, "unknown user_id — set it as the student's wearable_id")

    # نرفع تنبيهاً واحداً لأوّل عينة تتجاوز العتبة (والتهدئة داخل _process تمنع التكرار)
    for s in data.get("samples") or []:
        if s.get("is_daily_total"):
            continue  # نتجاهل المجاميع اليومية — العتبات للقراءات اللحظية فقط
        try:
            val = float(s.get("value"))
        except (TypeError, ValueError):
            continue
        result = _process(db, student, metric, val, False)
        if result and not result.get("suppressed"):
            return {"status": "alerted", "provider": data.get("provider"), **result}
    return {"status": "ok", "alert": None}


@router.post("/ow/ping")
async def open_wearables_ping(request: Request, db: Session = Depends(get_db)):
    """نبضة اختبار لزر «اختبار الاتصال» — تتحقق من الوصول والتفعيل والسرّ دون تنبيه."""
    secret = get_system_setting(db, "ow_webhook_secret") or OW_WEBHOOK_SECRET
    got = (request.headers.get("x-webhook-secret")
           or request.headers.get("authorization", "").removeprefix("Bearer ").strip())
    return {
        "status": "pong",
        "enabled": get_system_setting(db, "ow_enabled", True) is not False,
        "secret_configured": bool(secret),
        "secret_ok": (not secret) or (bool(got) and hmac.compare_digest(got, secret)),
        "svix_mode": bool(secret) and secret.startswith("whsec_"),
        "cooldown_s": _int_setting(db, "ow_cooldown_s", 300),
    }
