server {
    listen 8080;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;

    absolute_redirect off;
    port_in_redirect off;

    # Security headers (qa-loop iter-6 Cluster-E + Fix #171: TC-017 / TC-352 / TC-353 / TC-377).
    # HSTS max-age=15552000 (180d) + includeSubDomains + preload — 180d is the OWASP
    # minimum and matches the qa-loop test matrix canonical value (TC-352 strict-substring
    # `max-age=15552000`). TLS is mandatory for console.<sov>.
    # CSP: 'unsafe-inline'/'unsafe-eval' on script-src is required by Vite/React-runtime
    # bootstrap and Keycloak adapter; img/connect/font sources cover SSE (wss:),
    # avatar URLs (data:/https:), and webfont assets.
    # X-Frame-Options DENY: SPA is never legitimately framed (Keycloak login redirect
    # is a top-level navigation, not an iframe); align with hardened-baseline.
    add_header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; connect-src 'self' wss: https:; font-src 'self' data:; frame-ancestors 'none'; base-uri 'self'; form-action 'self' https:" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

    # Gzip
    gzip on;
    gzip_vary on;
    gzip_min_length 256;
    gzip_types text/html text/css text/javascript application/javascript application/json image/svg+xml;

    # Proxy API calls to catalyst-api service (K8s internal)
    # SSE support: disable buffering on log stream endpoint
    resolver 10.43.0.10 valid=30s;
    set $api_upstream http://catalyst-api.catalyst.svc.cluster.local:8080;

    location /api/ {
        proxy_pass $api_upstream;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Connection '';

        # SSE streaming
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 300s;
        chunked_transfer_encoding on;
        add_header X-Accel-Buffering no;

        # Re-emit security headers — nginx's add_header is inherited only when the
        # current level defines NO add_header at all; declaring X-Accel-Buffering
        # above shadows the server-level set, so we must restate them here.
        add_header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload" always;
        add_header X-Frame-Options "DENY" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
        add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; connect-src 'self' wss: https:; font-src 'self' data:; frame-ancestors 'none'; base-uri 'self'; form-action 'self' https:" always;
        add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
    }

    # Cache static assets
    location ~* \.(js|css|png|jpg|svg|woff2|ico)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";

        # Re-emit security headers (see /api/ comment for nginx inheritance rule).
        add_header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload" always;
        add_header X-Frame-Options "DENY" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
        add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; connect-src 'self' wss: https:; font-src 'self' data:; frame-ancestors 'none'; base-uri 'self'; form-action 'self' https:" always;
        add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
    }

    # SPA fallback — all routes serve index.html.
    # Vite is built with base: '/' so assets live at /assets/*.
    # On contabo (console.openova.io/sovereign/*), Traefik's strip-sovereign
    # Middleware rewrites /sovereign/assets/foo → /assets/foo before the
    # request reaches nginx. On Sovereigns (console.<sov>/), the HTTPRoute
    # passes through to / directly. Both paths resolve to /assets/* here.
    # React routes (/dashboard, /wizard, etc.) fall through to /index.html
    # and are handled client-side (issue #596).
    location / {
        try_files $uri /index.html;
    }

    # Health check (used by K8s probes)
    location = /healthz {
        access_log off;
        return 200 'ok';
        add_header Content-Type text/plain;
    }
}
