"""
EOL/EOS + Android patch-level checks for MDM (Intune) mobile devices.

Reuses eol_service (endoflife.date fetch/cache/EOLStatus/upsert). The only
NEW work is mapping an Intune device model → endoflife slug + release, since
endoflife keys phones by marketing name (Galaxy S25 Ultra, iPhone 15 Pro)
while Intune reports model identifiers:
  - Apple  reports the marketing name ("iPhone 15 Pro") → fuzzy-match the
    endoflife release label/name.
  - Samsung reports an SM-code ("SM-S938B") with NO textual overlap → a
    curated SM-prefix → endoflife-release table (extend as inventory grows).

Plus a cheap "Android security patch level is N months stale" finding from
Intune's androidSecurityPatchLevel — the control instance that checks whether
patches were actually applied, without scraping any vendor bulletin.
"""
from __future__ import annotations

import logging
import re
from datetime import date, datetime
from typing import Optional, Tuple

from sqlalchemy.orm import Session

from app.services import eol_service

logger = logging.getLogger(__name__)


# Curated Samsung SM-base-code → (endoflife slug, release `name`). Matched by
# prefix so the region/variant suffix (B/N/U/0/F…) is ignored: "SM-S938B"
# starts with "SM-S938". Names verified against endoflife.date.
# ponytail: curated; add a row when a new model shows up in inventory —
# unknown models are skipped (no false-positive), not guessed.
_PHONE = "samsung-mobile"
_TAB = "samsung-galaxy-tab"
# SM-base-code (4 digits) → (endoflife slug, release `name`). Codes are
# unique per model, so no prefix collides with another. Slug per row routes
# tablets (SM-X/T/P) to samsung-galaxy-tab. Names verified against the live
# endoflife.date API.
_SAMSUNG: list[Tuple[str, str, str]] = [
    # --- Galaxy S25 / S24 / S23 / S22 (base / + / Ultra / FE) ---
    ("SM-S938", _PHONE, "galaxy-s25-ultra"), ("SM-S936", _PHONE, "galaxy-s25+"), ("SM-S931", _PHONE, "galaxy-s25"), ("SM-S731", _PHONE, "galaxy-s25-fe"),
    ("SM-S928", _PHONE, "galaxy-s24-ultra"), ("SM-S926", _PHONE, "galaxy-s24+"), ("SM-S921", _PHONE, "galaxy-s24"), ("SM-S721", _PHONE, "galaxy-s24-fe"),
    ("SM-S918", _PHONE, "galaxy-s23-ultra"), ("SM-S916", _PHONE, "galaxy-s23+"), ("SM-S911", _PHONE, "galaxy-s23"), ("SM-S711", _PHONE, "galaxy-s23-fe"),
    ("SM-S908", _PHONE, "galaxy-s22-ultra"), ("SM-S906", _PHONE, "galaxy-s22+"), ("SM-S901", _PHONE, "galaxy-s22"),
    # --- Galaxy S21 / S20 (note the -5g suffix on S21) ---
    ("SM-G998", _PHONE, "galaxy-s21-ultra-5g"), ("SM-G996", _PHONE, "galaxy-s21+-5g"), ("SM-G991", _PHONE, "galaxy-s21-5g"), ("SM-G990", _PHONE, "galaxy-s21-fe-5g"),
    ("SM-G988", _PHONE, "galaxy-s20-ultra-5g"), ("SM-G986", _PHONE, "galaxy-s20+-5g"), ("SM-G985", _PHONE, "galaxy-s20+"),
    ("SM-G981", _PHONE, "galaxy-s20-5g"), ("SM-G980", _PHONE, "galaxy-s20"), ("SM-G781", _PHONE, "galaxy-s20-fe-5g"), ("SM-G780", _PHONE, "galaxy-s20-fe"),
    # --- Galaxy Note 20 / 10 ---
    ("SM-N986", _PHONE, "galaxy-note20-ultra-5g"), ("SM-N985", _PHONE, "galaxy-note20-ultra"), ("SM-N981", _PHONE, "galaxy-note20-5g"), ("SM-N980", _PHONE, "galaxy-note20"),
    ("SM-N976", _PHONE, "galaxy-note10+-5g"), ("SM-N975", _PHONE, "galaxy-note10+"), ("SM-N971", _PHONE, "galaxy-note10-5g"), ("SM-N970", _PHONE, "galaxy-note10"), ("SM-N770", _PHONE, "galaxy-note10-lite"),
    # --- Galaxy Z Fold / Flip ---
    ("SM-F966", _PHONE, "galaxy-z-fold7"), ("SM-F956", _PHONE, "galaxy-z-fold6"), ("SM-F946", _PHONE, "galaxy-z-fold5"), ("SM-F936", _PHONE, "galaxy-z-fold4"), ("SM-F926", _PHONE, "galaxy-z-fold3-5g"), ("SM-F916", _PHONE, "galaxy-z-fold2-5g"),
    ("SM-F766", _PHONE, "galaxy-z-flip7"), ("SM-F741", _PHONE, "galaxy-z-flip6"), ("SM-F731", _PHONE, "galaxy-z-flip5"), ("SM-F721", _PHONE, "galaxy-z-flip4"), ("SM-F711", _PHONE, "galaxy-z-flip3-5g"),
    # --- Galaxy A-series (5G + LTE) ---
    ("SM-A576", _PHONE, "galaxy-a57-5g"), ("SM-A566", _PHONE, "galaxy-a56-5g"), ("SM-A556", _PHONE, "galaxy-a55-5g"), ("SM-A546", _PHONE, "galaxy-a54-5g"), ("SM-A536", _PHONE, "galaxy-a53-5g"),
    ("SM-A376", _PHONE, "galaxy-a37-5g"), ("SM-A366", _PHONE, "galaxy-a36-5g"), ("SM-A356", _PHONE, "galaxy-a35-5g"), ("SM-A346", _PHONE, "galaxy-a34-5g"), ("SM-A336", _PHONE, "galaxy-a33-5g"),
    ("SM-A266", _PHONE, "galaxy-a26-5g"), ("SM-A256", _PHONE, "galaxy-a25-5g"), ("SM-A166", _PHONE, "galaxy-a16-5g"), ("SM-A165", _PHONE, "galaxy-a16"),
    ("SM-A156", _PHONE, "galaxy-a15-5g"), ("SM-A155", _PHONE, "galaxy-a15"), ("SM-A146", _PHONE, "galaxy-a14-5g"), ("SM-A145", _PHONE, "galaxy-a14"),
    # --- Galaxy XCover (rugged business) ---
    ("SM-G556", _PHONE, "galaxy-xcover7"), ("SM-G736", _PHONE, "galaxy-xcover6-pro"), ("SM-G525", _PHONE, "galaxy-xcover5"), ("SM-G715", _PHONE, "galaxy-xcover-pro"),
    ("SM-G398", _PHONE, "galaxy-xcover-4s"), ("SM-G390", _PHONE, "galaxy-xcover-4"), ("SM-G389", _PHONE, "galaxy-xcover3-g389f"), ("SM-G388", _PHONE, "galaxy-xcover-3"),
    # --- Galaxy Tab S10 / S9 / S8 / S7 / S6 (SM-X newer, SM-T/P older) ---
    ("SM-X926", _TAB, "galaxy-tab-s10-ultra"), ("SM-X826", _TAB, "galaxy-tab-s10+"),
    ("SM-X916", _TAB, "galaxy-tab-s9-ultra"), ("SM-X816", _TAB, "galaxy-tab-s9+"), ("SM-X716", _TAB, "galaxy-tab-s9"), ("SM-X710", _TAB, "galaxy-tab-s9"),
    ("SM-X616", _TAB, "galaxy-tab-s9-fe+"), ("SM-X610", _TAB, "galaxy-tab-s9-fe+"), ("SM-X516", _TAB, "galaxy-tab-s9-fe"), ("SM-X510", _TAB, "galaxy-tab-s9-fe"),
    ("SM-X906", _TAB, "galaxy-tab-s8-ultra"), ("SM-X806", _TAB, "galaxy-tab-s8+"), ("SM-X706", _TAB, "galaxy-tab-s8"), ("SM-X700", _TAB, "galaxy-tab-s8"),
    ("SM-T976", _TAB, "galaxy-tab-s7+"), ("SM-T970", _TAB, "galaxy-tab-s7+"), ("SM-T875", _TAB, "galaxy-tab-s7"), ("SM-T870", _TAB, "galaxy-tab-s7"), ("SM-T736", _TAB, "galaxy-tab-s7-fe"), ("SM-T730", _TAB, "galaxy-tab-s7-fe"),
    ("SM-T866", _TAB, "galaxy-tab-s6"), ("SM-T860", _TAB, "galaxy-tab-s6"), ("SM-P625", _TAB, "galaxy-tab-s6-lite-2024"), ("SM-P620", _TAB, "galaxy-tab-s6-lite-2024"), ("SM-P619", _TAB, "galaxy-tab-s6-lite"), ("SM-P613", _TAB, "galaxy-tab-s6-lite"), ("SM-P615", _TAB, "galaxy-tab-s6-lite-2020"), ("SM-P610", _TAB, "galaxy-tab-s6-lite-2020"),
    # --- Galaxy Tab A (budget) ---
    ("SM-X236", _TAB, "galaxy-tab-a11+"), ("SM-X230", _TAB, "galaxy-tab-a11+"), ("SM-X135", _TAB, "galaxy-tab-a11"), ("SM-X130", _TAB, "galaxy-tab-a11"),
    ("SM-X216", _TAB, "galaxy-tab-a9+"), ("SM-X210", _TAB, "galaxy-tab-a9+"), ("SM-X116", _TAB, "galaxy-tab-a9"), ("SM-X110", _TAB, "galaxy-tab-a9"), ("SM-X205", _TAB, "galaxy-tab-a8"), ("SM-X200", _TAB, "galaxy-tab-a8"),
    ("SM-T350", _TAB, "galaxy-tab-a-8.0-2015"), ("SM-T280", _TAB, "galaxy-tab-a-7.0-2016"),
    # --- Galaxy Tab Active (rugged tablets) ---
    ("SM-X356", _TAB, "galaxy-tab-active5-pro"), ("SM-X306", _TAB, "galaxy-tab-active5"), ("SM-X300", _TAB, "galaxy-tab-active5"), ("SM-T575", _TAB, "galaxy-tab-active3"),
    ("SM-T395", _TAB, "galaxy-tab-active2"), ("SM-T365", _TAB, "galaxy-tab-active-lte"), ("SM-T360", _TAB, "galaxy-tab-active"),
]

_STALE_CVE_ID = "ANDROID-PATCH-LEVEL-STALE"


def _norm(s: Optional[str]) -> str:
    return re.sub(r"[^a-z0-9]+", "-", (s or "").lower()).strip("-")


def _resolve_device(manuf: str, model: str, os_name: str):
    """→ (slug, matcher) or None. matcher = {'kind':'name','name':...} for the
    Samsung table, or {'kind':'apple','model':...} for Apple fuzzy match."""
    m = model or ""
    mu = (manuf or "").lower()
    ml = m.lower()
    osl = (os_name or "").lower()

    if "apple" in mu or ml.startswith("ipad") or ml.startswith("iphone") or "ios" in osl:
        slug = "ipad" if ("ipad" in ml or "ipados" in osl) else "iphone"
        return slug, {"kind": "apple", "model": m}

    if "samsung" in mu or re.match(r"sm-[a-z]\d", ml):
        up = m.upper()
        for prefix, slug, name in _SAMSUNG:
            if up.startswith(prefix):
                return slug, {"kind": "name", "name": name}
        logger.debug("mobile-eol: unmapped Samsung model %s", m)
    return None


def _find_release(data: dict, matcher: dict) -> Optional[dict]:
    rels = ((data.get("result") or {}).get("releases") or []) if isinstance(data, dict) else []
    if matcher["kind"] == "name":
        return next((r for r in rels if r.get("name") == matcher["name"]), None)
    target = _norm(matcher["model"])  # "iphone-15-pro-max" / "ipad-air-5th-generation"
    for r in rels:
        label = r.get("label") or ""
        cands = {_norm(r.get("name")), _norm(label),
                 _norm("iphone " + label), _norm("ipad " + label)}
        if target in cands:
            return r
    return None


def _parse_patch_date(raw) -> Optional[date]:
    s = str(raw or "").strip()[:10]
    if not s:
        return None
    try:
        return datetime.strptime(s, "%Y-%m-%d").date()
    except ValueError:
        return None


def _upsert_android_stale(db: Session, asset, patch_level: str, age_days: int) -> bool:
    """Pseudo-finding: the device's Android security patch level is stale.
    Stable cve_id per asset → idempotent. Returns True on create."""
    from app.models.vulnerability import Vulnerability, VulnerabilitySeverity, VulnerabilityStatus

    months = age_days // 30
    if age_days >= 365:
        sev, cvss = VulnerabilitySeverity.high, 8.0
    elif age_days >= 180:
        sev, cvss = VulnerabilitySeverity.medium, 5.5
    else:
        sev, cvss = VulnerabilitySeverity.low, 3.0

    title = f"Android security patch level {months} months behind ({patch_level})"
    desc = (f"Intune reports this device's Android security patch level as {patch_level} "
            f"— {age_days} days ({months} months) old. Monthly Android/OEM security "
            f"patches since then have not been applied, so any CVE fixed in those "
            f"bulletins remains open regardless of MDM patch policy.")

    existing = (db.query(Vulnerability)
                  .filter(Vulnerability.cve_id == _STALE_CVE_ID, Vulnerability.asset_id == asset.id)
                  .first())
    if existing:
        existing.severity = sev
        existing.cvss_score = cvss
        existing.title = title[:500]
        existing.description = desc
        existing.package_version = str(patch_level)[:100]
        existing.detected_at = datetime.now()
        from app.services.audit_events import reopen_if_patched
        reopen_if_patched(db, existing, reason="Mobile EOL/patch-level check reports this finding again", source="mobile_eol")
        try:
            existing.refresh_scores()
        except Exception:
            pass
        return False

    row = Vulnerability(
        cve_id=_STALE_CVE_ID, asset_id=asset.id, cvss_score=cvss, severity=sev,
        status=VulnerabilityStatus.open, title=title[:500], description=desc,
        package_name="Android Security Patch Level", package_version=str(patch_level)[:100],
        detected_at=datetime.now(), sources='["intune"]', first_detected_by="intune",
    )
    db.add(row)
    db.flush()
    try:
        row.refresh_scores()
    except Exception:
        pass
    return True


def check_device(db: Session, asset, device: dict) -> int:
    """EOL/EOS for the device model + Android patch-level staleness.
    Returns findings upserted. Caller commits."""
    count = 0
    manuf = (device.get("manufacturer") or "").strip()
    model = (device.get("model") or "").strip()
    os_name = (device.get("operatingSystem") or "").strip()
    os_version = (device.get("osVersion") or "").strip()

    # 1) Device-model EOL/EOS via endoflife.date.
    try:
        res = _resolve_device(manuf, model, os_name)
        if res:
            slug, matcher = res
            data = eol_service.fetch_product(db, slug)
            rel = _find_release(data, matcher) if data else None
            if rel:
                status = eol_service._build_eol_status(rel, slug)
                if status.is_eol or status.is_eol_soon or status.is_eoas:
                    # product_name is the VENDOR only — upsert_eol_vulnerability
                    # appends the release label itself, so passing "Samsung
                    # Galaxy Tab A8" would double it ("…A8 Galaxy Tab A8").
                    # endoflife labels: iPhone lacks the "iPhone" prefix, iPad
                    # and Samsung labels already carry it.
                    vendor = {"iphone": "Apple iPhone", "ipad": "Apple",
                              "samsung-mobile": "Samsung",
                              "samsung-galaxy-tab": "Samsung"}.get(slug, (manuf or "Device").title())
                    vid, _ = eol_service.upsert_eol_vulnerability(
                        db, asset_id=asset.id, product_name=vendor,
                        installed_version=(f"{os_name} {os_version}".strip() or "unknown"),
                        status=status,
                    )
                    # Keep the full device name in the package column.
                    if vid:
                        from app.models.vulnerability import Vulnerability
                        row = db.query(Vulnerability).filter(Vulnerability.id == vid).first()
                        if row:
                            row.package_name = f"{vendor} {rel.get('label') or model}".strip()[:255]
                    count += 1
    except Exception as e:
        logger.debug("mobile-eol device check failed for %s: %s", asset.hostname, e)

    # 2) Android security-patch-level staleness + per-CVE detail from ASB.
    if "android" in os_name.lower():
        patch = device.get("androidSecurityPatchLevel")
        d = _parse_patch_date(patch)
        if d:
            age = (date.today() - d).days
            if age >= 90:  # <90d = within a normal monthly-patch window
                try:
                    _upsert_android_stale(db, asset, str(patch)[:10], age)
                    count += 1
                except Exception as e:
                    logger.debug("mobile-eol android-patch failed for %s: %s", asset.hostname, e)
            # Per-CVE findings for the months the device is behind (Google ASB).
            try:
                from app.services import android_cve_service
                count += android_cve_service.check_android_cves(db, asset, patch, manufacturer=manuf)
            except Exception as e:
                logger.debug("android-asb CVEs failed for %s: %s", asset.hostname, e)

    return count
