"""AP-style inbox endpoint for receiving signed activities from the coop.

POST /inbox — verify HTTP Signature, dispatch by activity type.
"""
from __future__ import annotations

import logging
import time

import httpx
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse

from ..dependencies import get_redis_client
from ..utils.http_signatures import verify_request_signature, parse_key_id

log = logging.getLogger(__name__)
router = APIRouter()

# Cache fetched public keys in Redis for 24 hours
_KEY_CACHE_TTL = 86400


async def _fetch_actor_public_key(actor_url: str) -> str | None:
    """Fetch an actor's public key, with Redis caching."""
    redis = get_redis_client()
    cache_key = f"actor_pubkey:{actor_url}"

    # Check cache
    cached = redis.get(cache_key)
    if cached:
        return cached

    # Fetch actor JSON
    try:
        async with httpx.AsyncClient(timeout=10) as client:
            resp = await client.get(
                actor_url,
                headers={"Accept": "application/activity+json, application/ld+json"},
            )
            if resp.status_code != 200:
                log.warning("Failed to fetch actor %s: %d", actor_url, resp.status_code)
                return None
            data = resp.json()
    except Exception:
        log.warning("Error fetching actor %s", actor_url, exc_info=True)
        return None

    pub_key_pem = (data.get("publicKey") or {}).get("publicKeyPem")
    if not pub_key_pem:
        log.warning("No publicKey in actor %s", actor_url)
        return None

    # Cache it
    redis.set(cache_key, pub_key_pem, ex=_KEY_CACHE_TTL)
    return pub_key_pem


@router.post("/inbox")
async def inbox(request: Request):
    """Receive signed AP activities from the coop platform."""
    sig_header = request.headers.get("signature", "")
    if not sig_header:
        return JSONResponse({"error": "missing signature"}, status_code=401)

    # Read body
    body = await request.body()

    # Verify HTTP Signature
    actor_url = parse_key_id(sig_header)
    if not actor_url:
        return JSONResponse({"error": "invalid keyId"}, status_code=401)

    pub_key = await _fetch_actor_public_key(actor_url)
    if not pub_key:
        return JSONResponse({"error": "could not fetch public key"}, status_code=401)

    req_headers = dict(request.headers)
    path = request.url.path
    valid = verify_request_signature(
        public_key_pem=pub_key,
        signature_header=sig_header,
        method="POST",
        path=path,
        headers=req_headers,
    )
    if not valid:
        log.warning("Invalid signature from %s", actor_url)
        return JSONResponse({"error": "invalid signature"}, status_code=401)

    # Parse and dispatch
    try:
        activity = await request.json()
    except Exception:
        return JSONResponse({"error": "invalid json"}, status_code=400)

    activity_type = activity.get("type", "")
    log.info("Inbox received: %s from %s", activity_type, actor_url)

    if activity_type == "rose:DeviceAuth":
        _handle_device_auth(activity)

    # Always 202 — AP convention
    return JSONResponse({"status": "accepted"}, status_code=202)


def _handle_device_auth(activity: dict) -> None:
    """Set or delete did_auth:{device_id} in local Redis."""
    obj = activity.get("object", {})
    device_id = obj.get("device_id", "")
    action = obj.get("action", "")

    if not device_id:
        log.warning("rose:DeviceAuth missing device_id")
        return

    redis = get_redis_client()
    if action == "login":
        redis.set(f"did_auth:{device_id}", str(time.time()), ex=30 * 24 * 3600)
        log.info("did_auth set for device %s...", device_id[:16])
    elif action == "logout":
        redis.delete(f"did_auth:{device_id}")
        log.info("did_auth cleared for device %s...", device_id[:16])
    else:
        log.warning("rose:DeviceAuth unknown action: %s", action)
