"""SEO Writer — приём статей в Python. Django и FastAPI в одном файле.

Установка (Django):
1. Скопируйте раздел Django в приложение, например blog/seowriter.py
2. В urls.py:  path("api/seowriter", seowriter_webhook)
3. В settings.py или .env:  SEOWRITER_SECRET = "секрет из настроек сайта"
4. Замените тело save_article() на запись в вашу модель

Установка (FastAPI):
1. Скопируйте раздел FastAPI и подключите роутер к приложению
2. Секрет читается из переменной окружения SEOWRITER_SECRET

Важно:
- отвечайте 2xx сразу, тяжёлую работу отправляйте в очередь;
- обрабатывайте оба события: article.published и article.updated;
- защита от повторов — по article["id"], он не меняется между отправками;
- тело читается сырым (request.body) ДО разбора JSON: подписаны именно эти байты.
"""
import hashlib
import hmac
import json
import logging
import os
import time

logger = logging.getLogger(__name__)

SECRET = os.environ.get("SEOWRITER_SECRET", "")

# Возраст запроса, после которого он считается устаревшим. Требует, чтобы время
# на сервере было синхронным — иначе все запросы будут отвергаться.
TOLERANCE_SECONDS = 300


def signature_matches(raw: bytes, timestamp: str, header: str, secret: str = "") -> bool:
    """Подписей в заголовке может быть несколько через запятую — это окно смены
    секрета. Проверяем все, иначе смена секрета оборвёт публикации."""
    expected = hmac.new(
        (secret or SECRET).encode("utf-8"),
        timestamp.encode("utf-8") + b"." + raw,
        hashlib.sha256,
    ).hexdigest()
    for part in header.split(","):
        part = part.strip()
        if part.startswith("v1=") and hmac.compare_digest(expected, part[3:]):
            return True
    return False


def handle_payload(raw: bytes, timestamp: str, signature: str, event: str, secret: str = ""):
    """Общая логика для обоих фреймворков: возвращает (код ответа, тело)."""
    if not timestamp or not signature:
        return 401, {"ok": False, "error": "Нет подписи"}
    try:
        age = abs(time.time() - int(timestamp))
    except ValueError:
        return 401, {"ok": False, "error": "Некорректная отметка времени"}
    if age > TOLERANCE_SECONDS:
        return 401, {"ok": False, "error": "Запрос устарел — проверьте время на сервере"}
    if not signature_matches(raw, timestamp, signature, secret):
        return 401, {"ok": False, "error": "Подпись не совпала"}

    try:
        payload = json.loads(raw.decode("utf-8"))
    except Exception:
        return 400, {"ok": False, "error": "Тело запроса не разобрано"}

    if event == "ping":
        return 200, {
            "success": True,
            "cms": "Python",
            "site_title": "",
            # Уберите article.updated, только если обновления вам не нужны:
            # тогда обложка придёт сразу, но правки статьи не будут доходить
            "supported_events": ["article.published", "article.updated"],
        }
    if event == "category.list":
        return 200, {"categories": []}
    if event not in ("article.published", "article.updated"):
        return 200, {"ok": True, "skipped": True}

    article = payload.get("article") or {}
    if not article.get("id"):
        return 400, {"ok": False, "error": "В теле нет идентификатора статьи"}

    try:
        saved = save_article(article, event)
    except Exception as exc:
        # Пятисотка означает «повторите позже» — статья придёт снова
        return 500, {"ok": False, "error": str(exc)}

    return 200, {
        "ok": True,
        "id": saved.get("id"),
        # Верните адрес статьи: от него зависят внутренняя перелинковка,
        # отправка в IndexNow и анонсы в соцсетях
        "url": saved.get("url"),
    }


def save_article(article: dict, event: str) -> dict:
    """ЗАМЕНИТЕ на запись в вашу модель.

    Ищите запись по article["id"] и обновляйте её — тогда повторная доставка
    не создаст дубль. Пример для Django:

        post, _ = Post.objects.update_or_create(
            seowriter_id=article["id"],
            defaults={
                "title": article["h1"],
                "slug": article["slug"],
                "body": article["content_html"],
                "excerpt": article["excerpt"],
                "meta_title": article["meta"]["title"],
                "meta_description": article["meta"]["description"],
                "cover_url": (article.get("cover") or {}).get("url"),
                "is_published": article["status"] == "publish",
            },
        )
        return {"id": post.id, "url": f"https://example.com/blog/{post.slug}"}
    """
    logger.info("[seowriter] %s: %s", event, article.get("title"))
    return {"id": article["id"], "url": "https://example.com/blog/" + (article.get("slug") or "")}


# ── Django ─────────────────────────────────────────────────────────────────────

try:
    from django.http import JsonResponse
    from django.views.decorators.csrf import csrf_exempt
    from django.views.decorators.http import require_POST

    @csrf_exempt          # запрос приходит извне, csrf-токена у него нет
    @require_POST
    def seowriter_webhook(request):
        status, body = handle_payload(
            raw=request.body,
            timestamp=request.headers.get("X-SEOWriter-Timestamp", ""),
            signature=request.headers.get("X-SEOWriter-Signature", ""),
            event=request.headers.get("X-SEOWriter-Event", ""),
        )
        return JsonResponse(body, status=status)

except ImportError:  # Django не установлен — используется вариант FastAPI
    pass


# ── FastAPI ────────────────────────────────────────────────────────────────────

try:
    from fastapi import APIRouter, Request
    from fastapi.responses import JSONResponse

    seowriter_router = APIRouter()

    @seowriter_router.post("/api/seowriter")
    async def seowriter_endpoint(request: Request):
        status, body = handle_payload(
            raw=await request.body(),
            timestamp=request.headers.get("x-seowriter-timestamp", ""),
            signature=request.headers.get("x-seowriter-signature", ""),
            event=request.headers.get("x-seowriter-event", ""),
        )
        return JSONResponse(body, status_code=status)

except ImportError:  # FastAPI не установлен — используется вариант Django
    pass
