"""
HomeBase Public API — read-only public proxy to Turso.

Architecture: FastAPI app behind Cloudflare Tunnel (no Cloudflare Access).
- GET /             → serves homebase.html (the shared view)
- POST /turso       → validates SELECT-only SQL, forwards to Turso HTTP API
- GET /healthz      → liveness check
- Everything else   → 404

Security layers (defense in depth):
  1. Read-only Turso token (DB-level enforcement)
  2. SELECT-only SQL allowlist (proxy-level enforcement)
  3. Per-IP rate limiting (10 req/sec, burst 30)
  4. noindex header (prevents Google indexing)

This service is PUBLIC by design — no auth wall. Both the token scope
and the SQL allowlist prevent writes; the rate limit deters scraping
and bill-spiking.
"""
import os
import re
import time
import httpx
from collections import defaultdict, deque
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import FileResponse, JSONResponse
from pathlib import Path

# --- Config ---
TURSO_DATABASE_URL = os.environ["TURSO_DATABASE_URL"]
TURSO_AUTH_TOKEN = os.environ["TURSO_AUTH_TOKEN"]

TURSO_HTTP_URL = TURSO_DATABASE_URL.replace("libsql://", "https://") + "/v2/pipeline"
HOMEBASE_HTML = Path("/home/ubuntu/second-brain/homebase.html")

# SELECT-only — stricter than wbs-public's DML allowlist
ALLOWED_SQL_PATTERN = re.compile(r"^\s*SELECT\b", re.IGNORECASE)

# Rate limit: 10 req/sec per IP, with a 30-request burst window
RATE_LIMIT_WINDOW = 1.0   # seconds
RATE_LIMIT_BURST = 30     # max requests in any rolling window
_ip_hits: dict[str, deque] = defaultdict(deque)

app = FastAPI(title="HomeBase Public", docs_url=None, redoc_url=None)


def check_rate_limit(request: Request):
    """Sliding-window rate limit. Rejects with 429 if IP exceeds burst."""
    ip = request.headers.get("CF-Connecting-IP") or request.client.host
    now = time.time()
    hits = _ip_hits[ip]
    # Drop hits older than the window
    while hits and hits[0] < now - RATE_LIMIT_WINDOW:
        hits.popleft()
    if len(hits) >= RATE_LIMIT_BURST:
        raise HTTPException(status_code=429, detail="Rate limit exceeded")
    hits.append(now)


def validate_sql_statements(body: dict):
    """Walk the Turso pipeline payload; reject anything that isn't SELECT."""
    requests = body.get("requests", [])
    for req in requests:
        if req.get("type") != "execute":
            continue
        sql = req.get("stmt", {}).get("sql", "")
        if not ALLOWED_SQL_PATTERN.match(sql):
            raise HTTPException(
                status_code=403,
                detail=f"Only SELECT statements allowed. Got: {sql[:80]}",
            )


@app.get("/")
async def serve_homebase(request: Request):
    check_rate_limit(request)
    return FileResponse(
        HOMEBASE_HTML,
        headers={
            "X-Robots-Tag": "noindex, nofollow",
            "Referrer-Policy": "strict-origin-when-cross-origin",
        },
    )


@app.post("/turso")
async def turso_proxy(request: Request):
    check_rate_limit(request)
    body = await request.json()
    validate_sql_statements(body)

    async with httpx.AsyncClient(timeout=30) as client:
        resp = await client.post(
            TURSO_HTTP_URL,
            json=body,
            headers={"Authorization": f"Bearer {TURSO_AUTH_TOKEN}"},
        )
    return JSONResponse(content=resp.json(), status_code=resp.status_code)


@app.get("/healthz")
async def healthz():
    return {"status": "ok"}


@app.post("/vote")
async def vote(request: Request):
    check_rate_limit(request)
    body = await request.json()
    listing_id = body.get("listing_id")
    direction = body.get("direction")  # "up" or "down"
    if not listing_id or direction not in ("up", "down"):
        raise HTTPException(status_code=400, detail="Invalid vote")
    col = "votes_up" if direction == "up" else "votes_down"
    async with httpx.AsyncClient(timeout=30) as client:
        resp = await client.post(
            TURSO_HTTP_URL,
            json={"requests": [{"type": "execute", "stmt": {"sql": f"UPDATE apartment_listings SET {col} = COALESCE({col}, 0) + 1 WHERE id = ?", "args": [{"type": "text", "value": listing_id}]}}]},
            headers={"Authorization": f"Bearer {TURSO_AUTH_TOKEN}"},
        )
    return JSONResponse(content=resp.json(), status_code=resp.status_code)


@app.post("/geocode")
async def geocode(request: Request):
    check_rate_limit(request)
    body = await request.json()
    address = body.get("address", "")
    if not address:
        raise HTTPException(status_code=400, detail="Address required")
    async with httpx.AsyncClient(timeout=10) as client:
        resp = await client.get(
            "https://nominatim.openstreetmap.org/search",
            params={"q": address, "format": "json", "limit": 1},
            headers={"User-Agent": "HomeBase-SF/1.0"}
        )
    results = resp.json()
    if not results:
        return JSONResponse({"lat": None, "lng": None})
    return JSONResponse({"lat": float(results[0]["lat"]), "lng": float(results[0]["lon"])})
