"""before_request / after_request guards, caching and security headers."""
import time
from flask import Response, abort, flash, redirect, request, session, url_for

from nexora.config import PUBLIC_CACHE_SECONDS, STATIC_CACHE_SECONDS
from nexora.core import app
from nexora.database.connection import _public_response_cache, _public_response_cache_lock
from nexora.security.csrf import csrf_protect, generate_csrf_token
from nexora.security.permissions import has_permission


def _public_cache_key():
    if PUBLIC_CACHE_SECONDS <= 0 or request.method != "GET":
        return None
    if session.get("admin") or request.path.startswith(("/admin", "/static", "/media")):
        return None
    if request.args.get("_") or request.headers.get("Cache-Control") == "no-cache":
        return None
    if request.path in ("/", "/gallery", "/about", "/register") or request.endpoint in ("index", "gallery", "about", "register", "our_team"):
        return (request.path, tuple(sorted(request.args.items())))
    return None


@app.before_request
def _security_before_request():
    """Route-level guards + CSRF enforcement."""
    # Payment screenshots are sensitive: never serve them without an
    # authorized admin session, even though they live under static/.
    if request.path.lower().startswith(("/static/images/registrations/", "/media/images/registrations/")):
        if not session.get("admin") or not has_permission("view_payment_details"):
            abort(404)
    if request.method in ("POST", "PUT", "DELETE", "PATCH"):
        # Exempt login and static file paths from CSRF
        exempt_paths = {"/admin"}
        if request.path not in exempt_paths and not request.path.startswith("/static"):
            csrf_result = csrf_protect()
            if csrf_result is not None:
                return csrf_result
    elif request.method == "GET":
        # Generate CSRF token for all pages that have forms
        # (admin pages AND public registration/contact pages)
        if (request.path.startswith("/admin") or session.get("admin")
                or request.path in ("/register", "/about", "/")):
            generate_csrf_token()
    # Enforce session lifetime
    if session.get("admin") and session.get("_login_time"):
        try:
            login_ts = float(session["_login_time"])
            if time.time() - login_ts > app.config["PERMANENT_SESSION_LIFETIME"].total_seconds():
                session.clear()
                flash("Session expired. Please log in again.", "warning")
                return redirect(url_for("admin_login"))
        except (ValueError, TypeError):
            pass
    cache_key = _public_cache_key()
    if cache_key:
        now_ts = time.time()
        with _public_response_cache_lock:
            cached = _public_response_cache.get(cache_key)
        if cached and cached[0] > now_ts:
            body, status, headers, mimetype = cached[1]
            resp = Response(body, status=status, mimetype=mimetype)
            for key, value in headers.items():
                resp.headers[key] = value
            resp.headers["X-Nexora-Cache"] = "HIT"
            return resp


@app.after_request
def _secure_cookie_flag(response):
    """Mark the session cookie Secure only when the connection is HTTPS, so
    LAN HTTP testing keeps working while the HTTPS mirror gets hardened cookies.
    Also adds security headers and iOS Safari compatibility fixes."""
    # ── Security headers on every response ──
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-XSS-Protection"] = "1; mode=block"
    response.headers["X-Frame-Options"] = "SAMEORIGIN"
    response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"

    # ── iOS Safari cookie fix ──
    # iOS Safari ITP (Intelligent Tracking Prevention) blocks cookies on
    # cross-site requests. Ensure SameSite=Lax is always set and add
    # the Secure flag for HTTPS connections.
    if not app.config["SESSION_COOKIE_SECURE"] and request.is_secure:
        cookie_name = app.config.get("SESSION_COOKIE_NAME", "session")
        values = response.headers.getlist("Set-Cookie")
        updated = []
        changed = False
        for value in values:
            if value.startswith(cookie_name + "=") and "secure" not in value.lower():
                value += "; Secure"
                changed = True
            updated.append(value)
        if changed:
            del response.headers["Set-Cookie"]
            for value in updated:
                response.headers.add("Set-Cookie", value)

    # ── Cache control ──
    if request.path.startswith(("/static/", "/media/")) and response.status_code == 200:
        if request.path.lower().endswith((".css", ".js")):
            response.cache_control.no_cache = None
            response.cache_control.public = True
            response.cache_control.max_age = STATIC_CACHE_SECONDS
            response.cache_control.immutable = True
            response.headers["Vary"] = "Accept-Encoding"
        else:
            response.cache_control.no_cache = None
            response.cache_control.public = True
            response.cache_control.max_age = STATIC_CACHE_SECONDS
            response.cache_control.immutable = True
    elif request.path.startswith("/admin") or session.get("admin"):
        # iOS Safari can restore admin pages from its normal-mode page cache.
        # That leaves old CSRF/session state behind and turns form submissions
        # into confusing 405/403 JSON pages. Admin pages must always be fresh.
        response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
        response.headers["Pragma"] = "no-cache"
        response.headers["Expires"] = "0"
    elif response.status_code == 200 and response.mimetype == "text/html":
        # Public HTML pages must be fresh on phones after cPanel updates.
        response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
        response.headers["Pragma"] = "no-cache"
        response.headers["Expires"] = "0"
        response.headers["Vary"] = "Accept-Encoding, Cookie"

    cache_key = _public_cache_key()
    if response.headers.get("X-Nexora-Cache") == "HIT":
        return response
    if cache_key and response.status_code == 200 and response.mimetype == "text/html" and "Set-Cookie" not in response.headers:
        body = response.get_data()
        headers = {
            "Cache-Control": f"public, max-age={PUBLIC_CACHE_SECONDS}",
        }
        with _public_response_cache_lock:
            _public_response_cache[cache_key] = (
                time.time() + PUBLIC_CACHE_SECONDS,
                (body, response.status_code, headers, response.mimetype),
            )
        response.headers["Cache-Control"] = headers["Cache-Control"]
        response.headers["X-Nexora-Cache"] = "MISS"
    return response
