"""Shared config, Jinja env, render helper, auth dependencies, event logging."""

from __future__ import annotations

import json
import os
from pathlib import Path
from typing import Optional

from fastapi import HTTPException, Request
from fastapi.responses import HTMLResponse
from jinja2 import Environment, FileSystemLoader, select_autoescape

import auth
import viewas
from db import execute, query, query_one
from version import VERSION

# ---------- Config ----------

DATA_DIR = Path(os.environ.get("DATA_DIR", "/data"))
APP_BASE_URL = os.environ.get("APP_BASE_URL", "http://localhost:8000").rstrip("/")
EMAIL_FROM = os.environ.get("EMAIL_FROM", "SubmitStream <forms@submitstream.bowden.works>")
RESEND_API_KEY = os.environ.get("RESEND_API_KEY", "").strip()

# New forms start with this origin allowlist so they work on test sites immediately
# but are never fully open. *.demoing.info = all of rian's gated test subdomains.
# Add the client's live domain on top at go-live.
DEFAULT_ALLOWED_ORIGINS = os.environ.get("DEFAULT_ALLOWED_ORIGINS", "*.demoing.info")

# Print emails to the log instead of sending when explicitly asked OR when the
# Resend key is missing/placeholder. Lets the pipeline be tested before email is wired.
_dev_flag = os.environ.get("DEV_EMAIL_PRINT", "0") == "1"
_key_unusable = (not RESEND_API_KEY) or RESEND_API_KEY.startswith("REPLACE_WITH")
DEV_EMAIL_PRINT = _dev_flag or _key_unusable

# Public submit limits (defense-in-depth caps; per-form rate limit is separate).
MAX_FIELDS = 60
MAX_FIELD_LEN = 20_000
MAX_BODY_BYTES = 256 * 1024

BASE_DIR = Path(__file__).parent
TEMPLATES_DIR = BASE_DIR / "templates"
STATIC_DIR = BASE_DIR / "static"

# ---------- Jinja ----------

jinja_env = Environment(
    loader=FileSystemLoader(TEMPLATES_DIR),
    autoescape=select_autoescape(["html"]),
    trim_blocks=True,
    lstrip_blocks=True,
)
jinja_env.globals["app_version"] = VERSION
jinja_env.globals["app_base_url"] = APP_BASE_URL


def render(template_name: str, status_code: int = 200, **context) -> HTMLResponse:
    tmpl = jinja_env.get_template(template_name)
    return HTMLResponse(tmpl.render(**context), status_code=status_code)


# ---------- Auth dependencies ----------

def current_user(request: Request) -> Optional[auth.CurrentUser]:
    uid = auth.read_session_cookie(request.cookies.get(auth.SESSION_COOKIE))
    return auth.load_user(uid) if uid is not None else None


def require_user(request: Request) -> auth.CurrentUser:
    user = current_user(request)
    if user is None:
        raise HTTPException(status_code=303, headers={"location": "/"})
    return user


def require_admin(request: Request) -> auth.CurrentUser:
    user = require_user(request)
    if not user.is_admin:
        raise HTTPException(status_code=403, detail="Admin only")
    return user


def require_creator(request: Request) -> auth.CurrentUser:
    """admin or manager (may create forms/clients)."""
    user = require_user(request)
    if not user.can_create:
        raise HTTPException(status_code=403, detail="Manager or admin only")
    return user


# ---------- Form access model ----------

def forms_for(user: auth.CurrentUser):
    """Forms a user may view, newest first.
    admin → all; manager → created or granted; client → granted (incl. legacy owner)."""
    if user.is_admin:
        return query("SELECT * FROM forms ORDER BY created_at DESC")
    if user.role == "manager":
        return query(
            "SELECT * FROM forms WHERE created_by = ? "
            "OR id IN (SELECT form_id FROM form_access WHERE client_id = ?) "
            "ORDER BY created_at DESC", (user.id, user.id))
    return query(
        "SELECT * FROM forms WHERE id IN "
        "(SELECT form_id FROM form_access WHERE client_id = ?) ORDER BY created_at DESC",
        (user.id,))


def user_can_access_form(user: auth.CurrentUser, form_row) -> bool:
    if user.is_admin:
        return True
    if form_row is None:
        return False
    if user.role == "manager" and form_row["created_by"] == user.id:
        return True
    granted = query_one(
        "SELECT 1 FROM form_access WHERE form_id = ? AND client_id = ?",
        (form_row["id"], user.id))
    return granted is not None


# ---------- Management-API auth (token, on-server only) ----------

def require_api_user(request: Request) -> auth.CurrentUser:
    """Bearer-token auth for /api/*. Rejects anything proxied through Caddy
    (public): the management API is only reachable by direct on-host calls to the
    container bridge, so it is never an internet-facing surface."""
    if request.headers.get("x-forwarded-for") or request.headers.get("x-forwarded-host"):
        raise HTTPException(status_code=403, detail="management API is on-server only")
    authz = request.headers.get("authorization", "")
    token = authz[7:].strip() if authz.lower().startswith("bearer ") else ""
    user = auth.load_user_by_token(token)
    if not user:
        raise HTTPException(status_code=401, detail="invalid or missing API token")
    if not user.can_create:
        raise HTTPException(status_code=403, detail="token owner lacks create permission")
    return user


# ---------- View-as (standard bw_view_as-backed impersonation) ----------

def _valid_viewas_target(request: Request, real: auth.CurrentUser) -> Optional[int]:
    """The view-as target from the signed cookie, re-authorized on every request
    (a revoked/stale grant stops working immediately). None if not impersonating."""
    if not viewas.can_use(real.role):
        return None
    target = auth.read_viewas_cookie(request.cookies.get(auth.VIEWAS_COOKIE))
    if target is None or not viewas.can_view_as(str(real.id), str(target)):
        return None
    return target


def dashboard_identity(request: Request) -> tuple[auth.CurrentUser, Optional[auth.CurrentUser]]:
    """Returns (effective_user, impersonator). While viewing-as, effective_user is the
    impersonated account and impersonator is the real admin/manager; else (real, None)."""
    real = require_user(request)
    target = _valid_viewas_target(request, real)
    if target is not None:
        imp = auth.load_user(target)
        if imp:
            return imp, real
    return real, None


def impersonation_active(request: Request) -> bool:
    """True if the requester currently has a valid read-only view-as in effect. Used by
    the write-guard middleware to enforce read-only while impersonating."""
    real = current_user(request)
    return real is not None and _valid_viewas_target(request, real) is not None


# ---------- Event logging (per-submission debug timeline) ----------

def log_event(message: str, *, submission_id: Optional[int] = None,
              form_id: Optional[int] = None, level: str = "info",
              data: Optional[dict] = None) -> None:
    execute(
        "INSERT INTO events (submission_id, form_id, level, message, data_json) "
        "VALUES (?, ?, ?, ?, ?)",
        (submission_id, form_id, level, message, json.dumps(data) if data else None),
    )
