"""URL-shortener API — sample app for the Cloud Infrastructure & DevOps course.

Plan A delivers a skeletal in-memory implementation. Lesson 03 replaces
the in-memory store with Postgres via docker-compose. Lesson 12 adds
Prometheus instrumentation (/metrics endpoint, REQ counter, LAT histogram).
"""
from __future__ import annotations

import os
import secrets
import string
import time
from typing import Annotated

from fastapi import FastAPI, HTTPException, Request, Response
from fastapi.responses import RedirectResponse
from pydantic import BaseModel, HttpUrl
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST

REQ = Counter(
    "http_requests_total",
    "Total HTTP requests",
    ["route", "code"],
)
LAT = Histogram(
    "http_request_duration_seconds",
    "HTTP request latency in seconds",
    ["route"],
)

BASE_URL = os.environ.get("BASE_URL", "http://localhost:8000").rstrip("/")
CODE_ALPHABET = string.ascii_letters + string.digits
CODE_LENGTH = 6

app = FastAPI(title="URL Shortener", version="0.1.0")

# In-process store. Replaced by Postgres in Lesson 03's docker-compose stack.
URLS: dict[str, str] = {}


@app.middleware("http")
async def _prometheus_middleware(request: Request, call_next):
    start = time.time()
    response = await call_next(request)
    route = request.scope.get("endpoint", None)
    route_name = route.__name__ if route is not None else request.url.path
    LAT.labels(route=route_name).observe(time.time() - start)
    REQ.labels(route=route_name, code=str(response.status_code)).inc()
    return response


@app.get("/metrics", include_in_schema=False)
def metrics():
    return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST)


class ShortenRequest(BaseModel):
    url: HttpUrl


class ShortenResponse(BaseModel):
    code: str
    short_url: str


def _new_code() -> str:
    """Generate a short code, retrying on collision."""
    for _ in range(8):
        code = "".join(secrets.choice(CODE_ALPHABET) for _ in range(CODE_LENGTH))
        if code not in URLS:
            return code
    raise HTTPException(status_code=500, detail="exhausted code space")


@app.get("/health")
def health() -> dict[str, str]:
    return {"status": "ok"}


@app.post("/shorten", response_model=ShortenResponse)
def shorten(req: ShortenRequest) -> ShortenResponse:
    code = _new_code()
    URLS[code] = str(req.url)
    return ShortenResponse(code=code, short_url=f"{BASE_URL}/{code}")


@app.get("/{code}")
def follow(code: Annotated[str, "short code"]) -> RedirectResponse:
    target = URLS.get(code)
    if target is None:
        raise HTTPException(status_code=404, detail="unknown code")
    return RedirectResponse(url=target, status_code=302)
