"""Service Qualys Tags V3 — CRUD + analyse gap vs catalogue.

Limitations API Qualys (confirmées par la V3) :
- Tag dynamique : creation impossible via API -> UI console uniquement
- Tag statique : creation + assignment OK via API
- Lecture tous tags (avec ruleType DYNAMIC/STATIC) OK
"""
import os
import requests
from pathlib import Path
from sqlalchemy import text

# Path vers le catalogue V3
CATALOG_PATH = Path(__file__).parent.parent.parent / "deploy" / "qualys_tags_v3.yaml"


def _get_creds(db):
    """Retourne (url, user, pass, proxy) depuis app_secrets.

    Respecte le flag `qualys_bypass_proxy` (settings UI): si coche,
    le proxy est ignore (chaine vide) meme si `qualys_proxy` est defini.
    """
    from .secrets_service import get_secret
    proxy = get_secret(db, "qualys_proxy") or ""
    bypass = (get_secret(db, "qualys_bypass_proxy") or "").lower() == "true"
    if bypass:
        proxy = ""
    return (
        get_secret(db, "qualys_url"),
        get_secret(db, "qualys_user"),
        get_secret(db, "qualys_pass"),
        proxy,
    )


def _qualys_post(db, endpoint, payload, timeout=60):
    """POST sur Qualys QPS REST 2.0."""
    url, user, pwd, proxy = _get_creds(db)
    if not user:
        return {"ok": False, "msg": "Credentials Qualys non configures"}
    proxies = {"https": proxy, "http": proxy} if proxy else None
    try:
        r = requests.post(
            f"{url}{endpoint}",
            json=payload, auth=(user, pwd),
            verify=False, timeout=timeout, proxies=proxies,
            headers={"X-Requested-With": "PatchCenter", "Content-Type": "application/json"},
        )
        return {"ok": r.status_code == 200, "status": r.status_code, "text": r.text}
    except Exception as e:
        return {"ok": False, "msg": str(e)[:200]}


def _parse_xml_text(text_block, tag):
    """Extrait <tag>valeur</tag>, unescape entites (boucle si double-escape Qualys)."""
    import re, html
    m = re.search(f"<{tag}>(.*?)</{tag}>", text_block, re.DOTALL)
    if not m:
        return ""
    v = m.group(1).strip()
    for _ in range(3):
        u = html.unescape(v)
        if u == v:
            break
        v = u
    return v


def list_qualys_tags(db):
    """Liste tous les tags Qualys (nom, id, type DYN/STAT, ruleText)."""
    # search all tags
    payload = {"ServiceRequest": {"preferences": {"limitResults": 1000}}}
    r = _qualys_post(db, "/qps/rest/2.0/search/am/tag", payload)
    if not r.get("ok"):
        return {"ok": False, "msg": r.get("msg") or f"HTTP {r.get('status')}", "tags": []}
    tags = []
    for block in r["text"].split("<Tag>")[1:]:
        block = block.split("</Tag>")[0]
        tid = _parse_xml_text(block, "id")
        name = _parse_xml_text(block, "name")
        rule_type = _parse_xml_text(block, "ruleType")
        rule_text = _parse_xml_text(block, "ruleText")
        color = _parse_xml_text(block, "color")
        if tid and name:
            tags.append({
                "id": int(tid),
                "name": name,
                "type": "DYN" if rule_type else "STAT",
                "rule_type": rule_type,
                "rule_text": rule_text,
                "color": color,
            })
    return {"ok": True, "tags": tags}


def load_catalog():
    """Charge le catalogue YAML V3."""
    try:
        import yaml
    except ImportError:
        return {"error": "pyyaml non installe (pip install pyyaml)"}
    if not CATALOG_PATH.exists():
        return {"error": f"Catalogue introuvable: {CATALOG_PATH}"}
    with open(CATALOG_PATH, "r", encoding="utf-8") as f:
        return yaml.safe_load(f)


def analyze_gap(db):
    """Compare catalogue V3 vs tags Qualys. Retourne missing/extra/mismatch."""
    catalog = load_catalog()
    if "error" in catalog:
        return {"ok": False, "msg": catalog["error"]}

    ql = list_qualys_tags(db)
    if not ql.get("ok"):
        return ql

    qualys_by_name = {t["name"]: t for t in ql["tags"]}
    catalog_tags = []
    for cat_name, cat in catalog.get("categories", {}).items():
        for t in cat.get("tags", []) or []:
            catalog_tags.append({
                "category": cat_name,
                "name": t["name"],
                "type": t["type"],
                "auto": t.get("auto", False),
                "qql": t.get("qql", ""),
                "color": t.get("color", ""),
                "description": t.get("description", ""),
            })

    missing_dyn = []   # Dans catalogue, pas dans Qualys, type DYN -> creer en console
    missing_stat = []  # Dans catalogue, pas dans Qualys, type STAT -> creable via API
    mismatch = []      # Present mais type different (DYN vs STAT)
    present = []       # OK

    for c in catalog_tags:
        q = qualys_by_name.get(c["name"])
        if not q:
            if c["type"] == "DYN":
                missing_dyn.append(c)
            else:
                missing_stat.append(c)
        elif c["type"] != q["type"]:
            mismatch.append({"catalog": c, "qualys": q})
        else:
            present.append({"catalog": c, "qualys": q})

    # Tags Qualys pas dans le catalogue (pour info)
    catalog_names = {c["name"] for c in catalog_tags}
    extra = [t for t in ql["tags"] if t["name"] not in catalog_names]

    return {
        "ok": True,
        "present": present,
        "missing_dyn": missing_dyn,
        "missing_stat": missing_stat,
        "mismatch": mismatch,
        "extra": extra,
        "total_catalog": len(catalog_tags),
        "total_qualys": len(ql["tags"]),
    }


def create_static_tag(db, name, color="", description=""):
    """Cree un tag statique Qualys via API."""
    payload = {
        "ServiceRequest": {
            "data": {
                "Tag": {
                    "name": name,
                    "color": color or "#607D8B",
                    "description": description,
                }
            }
        }
    }
    r = _qualys_post(db, "/qps/rest/2.0/create/am/tag", payload)
    if r.get("ok"):
        return {"ok": True, "msg": f"Tag '{name}' cree"}
    return {"ok": False, "msg": r.get("msg") or f"HTTP {r.get('status')}: {r.get('text', '')[:200]}"}


def delete_tag(db, tag_id):
    """Supprime un tag Qualys par id."""
    r = _qualys_post(db, f"/qps/rest/2.0/delete/am/tag/{tag_id}", {})
    return {"ok": r.get("ok"), "msg": r.get("msg") or f"HTTP {r.get('status')}"}


def generate_console_steps(missing_dyn):
    """Retourne un texte avec les etapes console Qualys pour chaque tag DYN manquant."""
    steps = []
    for t in missing_dyn:
        steps.append({
            "name": t["name"],
            "category": t["category"],
            "qql": t["qql"],
            "color": t["color"],
            "steps": [
                "VMDR > Assets > Tags > New Tag",
                f"Name: {t['name']}",
                f"Color: {t['color']}",
                "Cocher 'Create as Dynamic Tag'",
                f"Asset Criteria: {t['qql']}",
                "Sauvegarder",
            ],
        })
    return steps
