import logging
import os
import re
import subprocess
from typing import Dict, Optional

from oauthenticator.generic import GenericOAuthenticator
from jupyterhub.auth import DummyAuthenticator
from dockerspawner import DockerSpawner

c = get_config()

# Authenticator selection (environment-driven)
c.Authenticator.enable_auth_state = False
auth_strategy = os.environ.get("JUPYTERHUB_AUTH_STRATEGY", "oauth").strip().lower()

if auth_strategy == "dummy":
    c.JupyterHub.authenticator_class = DummyAuthenticator
    dummy_password = os.environ.get("JUPYTERHUB_DUMMY_PASSWORD")
    if dummy_password:
        c.DummyAuthenticator.password = dummy_password
else:
    c.JupyterHub.authenticator_class = GenericOAuthenticator
    c.GenericOAuthenticator.client_id = os.environ.get("DSP_OAUTH_CLIENT_ID", "")
    c.GenericOAuthenticator.client_secret = os.environ.get("DSP_OAUTH_CLIENT_SECRET", "")
    c.GenericOAuthenticator.authorize_url = os.environ.get("DSP_OAUTH_AUTHORIZE_URL", "")
    c.GenericOAuthenticator.token_url = os.environ.get("DSP_OAUTH_TOKEN_URL", "")
    c.GenericOAuthenticator.userdata_url = os.environ.get("DSP_OAUTH_USERINFO_URL", "")
    c.GenericOAuthenticator.oauth_callback_url = os.environ.get("JUPYTERHUB_OAUTH_CALLBACK", "")
    c.GenericOAuthenticator.scope = ["profile"]
    c.GenericOAuthenticator.username_claim = "hub_username"
    c.GenericOAuthenticator.username_key = "hub_username"
    c.GenericOAuthenticator.auto_login = True

# Explicitly acknowledge HTTP when running behind an external TLS terminator.
c.JupyterHub.confirm_no_ssl = True
c.Spawner.http_timeout = int(os.getenv("JUPYTERHUB_HTTP_TIMEOUT", "90"))
c.Spawner.start_timeout = int(os.getenv("JUPYTERHUB_START_TIMEOUT", "90"))

def _env_bool(name: str, default: bool = False) -> bool:
    value = os.getenv(name)
    if value is None:
        return default
    return value.strip().lower() in {"1", "true", "yes", "on"}


tornado_settings = getattr(c.JupyterHub, "tornado_settings", {})
if isinstance(tornado_settings, dict):
    merged_settings = tornado_settings.copy()
else:
    merged_settings = {}

default_frame_ancestors = ["'self'", "http://localhost:8082", "http://127.0.0.1:8082"]
app_origin_values = [value.rstrip("/") for value in os.getenv("DSP_APP_ORIGINS", "").split() if value]
extra_frame_ancestors = [value.rstrip("/") for value in os.getenv("DSP_FRAME_ANCESTORS", "").split() if value]
frame_ancestors = " ".join(
    dict.fromkeys(default_frame_ancestors + app_origin_values + extra_frame_ancestors)
)

header_settings = {
    "Content-Security-Policy": f"frame-ancestors {frame_ancestors}",
    "X-Frame-Options": "ALLOWALL",
}

existing_headers = merged_settings.get("headers", {})
existing_headers.update(header_settings)
merged_settings["headers"] = existing_headers

external_url = os.getenv("JUPYTER_EXTERNAL_URL", "")
cookie_secure_default = external_url.startswith("https://")
merged_settings["cookie_options"] = {
    "SameSite": "None",
    "Secure": _env_bool("JUPYTERHUB_COOKIE_SECURE", cookie_secure_default),
}
c.JupyterHub.tornado_settings = merged_settings

# Single-user server configuration
c.JupyterHub.spawner_class = DockerSpawner
c.DockerSpawner.image = os.environ.get("DSP_JH_IMAGE", "jupyter/minimal-notebook:python-3.11")
c.DockerSpawner.remove_containers = True
c.DockerSpawner.cmd = ["start-singleuser.sh"]
c.DockerSpawner.notebook_dir = "/home/jovyan/work"
c.DockerSpawner.network_name = os.environ.get("DSP_JH_NETWORK", "dsp_default")


def _workspace_volume(username: str) -> Dict[str, str]:
    safe = re.sub(r"[^a-zA-Z0-9._-]+", "-", username)
    host_root = os.environ.get("DSP_WORKSPACE_ROOT", "/var/www/html/uploads/jupyter_workspace")
    volumes: Dict[str, str] = {f"{host_root}/{safe}": "/home/jovyan/work"}

    r_scripts_root = os.environ.get("DSP_R_SCRIPTS_ROOT", "/var/www/html/r_scripts")
    if os.path.isdir(r_scripts_root):
        volumes[r_scripts_root] = "/home/jovyan/work/r_scripts"

    return volumes


def _extract_person_id(username: str) -> Optional[str]:
    match = re.search(r"(\d+)$", username or "")
    return match.group(1) if match else None


def _run_sync(person_id: str) -> None:
    command = [
        "docker",
        "exec",
        os.environ.get("DSP_APP_CONTAINER", "dsp_app"),
        "php",
        "/var/www/html/scripts/trigger_workspace_sync.php",
        person_id,
    ]
    subprocess.run(command, check=False)


async def pre_spawn_hook(spawner):
    username = spawner.user.name
    spawner.volumes = _workspace_volume(username)
    person_id = _extract_person_id(username)
    if person_id:
        _run_sync(person_id)


c.DockerSpawner.pre_spawn_hook = pre_spawn_hook

_cull_token = os.environ.get("JUPYTERHUB_CULL_API_TOKEN")

if _cull_token:
    c.JupyterHub.services = [
        {
            "name": "cull-idle",
            "command": [
                "python",
                "-m",
                "jupyterhub_idle_culler",
                "--timeout=3600",
                "--cull-every=600",
                "--concurrency=10",
            ],
            "api_token": _cull_token,
            "admin": True,
        }
    ]
else:
    logging.warning("JUPYTERHUB_CULL_API_TOKEN not set; idle culler disabled.")
