#!/usr/bin/env python3
"""
kb-audit-dns — резолвит все домены из NPM API через публичный DNS (8.8.8.8),
сравнивает с локальным резолвом, детектит NXDOMAIN и расхождения.
Пишет audit/YYYY-MM-DD-dns-drift.md.
"""

import json
import ssl
import subprocess
import sys
import urllib.request
from datetime import date
from pathlib import Path

NPM_URL = "https://npm.dttb.ru"
NPM_USER = "it5870@yandex.ru"
NPM_PASS = "1qaz!QAZ"

VAULT = Path(__file__).resolve().parent.parent
OUT_DIR = VAULT / "audit"

_CTX = ssl.create_default_context()
_CTX.check_hostname = False
_CTX.verify_mode = ssl.CERT_NONE


def fetch_npm_domains():
    """Берём домены из NPM API (вместо парсинга markdown — свежее)."""
    req = urllib.request.Request(
        f"{NPM_URL}/api/tokens",
        data=json.dumps({"identity": NPM_USER, "secret": NPM_PASS}).encode(),
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    token = json.loads(urllib.request.urlopen(req, context=_CTX, timeout=10).read())["token"]
    req2 = urllib.request.Request(
        f"{NPM_URL}/api/nginx/proxy-hosts",
        headers={"Authorization": f"Bearer {token}"},
    )
    hosts = json.loads(urllib.request.urlopen(req2, context=_CTX, timeout=10).read())
    domains = set()
    for h in hosts:
        for d in h.get("domain_names", []):
            domains.add(d)
    return sorted(domains)


def dig(domain: str, server: str = "8.8.8.8") -> list[str]:
    """dig +short @server domain → список IP (пусто если NXDOMAIN/недоступен)."""
    try:
        r = subprocess.run(
            ["dig", f"@{server}", "+short", "+time=3", "+tries=1", domain, "A"],
            capture_output=True, text=True, timeout=8,
        )
        return [l.strip() for l in r.stdout.splitlines() if l.strip() and not l.startswith(";")]
    except (subprocess.TimeoutExpired, FileNotFoundError):
        return []


def main():
    today = date.today().isoformat()
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    out = OUT_DIR / f"{today}-dns-drift.md"

    try:
        domains = fetch_npm_domains()
    except Exception as e:
        print(f"NPM fetch fail: {e}", file=sys.stderr)
        sys.exit(1)

    if len(domains) < 3:
        print(f"safety abort: domains {len(domains)} < 3", file=sys.stderr)
        sys.exit(2)

    results = []
    for d in domains:
        public = dig(d, "8.8.8.8")
        local = dig(d, "10.0.0.1")  # OpenWrt router
        results.append((d, public, local))

    no_public = [r for r in results if not r[1]]
    split = [r for r in results if r[1] and r[2] and set(r[1]) != set(r[2])]
    no_local = [r for r in results if not r[2]]

    lines = [
        "---",
        f"date: {today}",
        "type: audit",
        "source: kb-audit-dns.py",
        "tags: [audit, dns]",
        "---",
        "",
        f"# DNS resolve audit — {today}",
        "",
        f"Резолвим все домены из NPM через публичный DNS (8.8.8.8) и локальный роутер (10.0.0.1).",
        "",
        f"- Всего доменов: **{len(domains)}**",
        f"- NXDOMAIN на 8.8.8.8: {len(no_public)} / пустой ответ локально: {len(no_local)} / split-horizon: {len(split)}",
        "",
    ]

    if no_public:
        lines += ["## ❌ NXDOMAIN / не резолвится на 8.8.8.8 (публичный DNS)", ""]
        lines += ["| Домен | Локальный IP |", "|---|---|"]
        for d, _, loc in no_public:
            lines.append(f"| `{d}` | {','.join(loc) or '(тоже нет)'} |")
        lines += [""]

    if split:
        lines += ["## ⚠ Split-horizon — разные IP снаружи и внутри", "",
                  "Это нормально для *.dttb.ru (внешний Let's Encrypt IP vs локальный 10.0.0.195). Но неожиданный split может быть багом.", ""]
        lines += ["| Домен | Публичный (8.8.8.8) | Локальный (10.0.0.1) |", "|---|---|---|"]
        for d, pub, loc in split:
            lines.append(f"| `{d}` | {','.join(pub)} | {','.join(loc)} |")
        lines += [""]

    if no_local:
        lines += ["## ⚠ Пустой локальный резолв (роутер не знает)", ""]
        for d, pub, _ in no_local:
            lines.append(f"- `{d}` (публичный: {','.join(pub) or '-'})")
        lines += [""]

    lines += ["## Полная таблица резолва", "",
              "| Домен | 8.8.8.8 | 10.0.0.1 |", "|---|---|---|"]
    for d, pub, loc in results:
        lines.append(f"| `{d}` | {','.join(pub) or '—'} | {','.join(loc) or '—'} |")
    lines += ["", "---", "*Автоматически через `scripts/kb-audit-dns.py`.*"]

    out.write_text("\n".join(lines))
    print(f"dns drift: {out}")
    print(f"  no_public: {len(no_public)} / split: {len(split)} / no_local: {len(no_local)}")


if __name__ == "__main__":
    main()
