"""Internal-only deployment boundary for the operator web UI (#435)."""

from __future__ import annotations

import ipaddress
import os
import re
from dataclasses import asdict, dataclass
from typing import Literal

Disposition = Literal["safe", "warn", "refuse"]

_LOOPBACK_HOSTS = frozenset({"127.0.0.1", "localhost", "::1"})
_ALL_INTERFACE_HOSTS = frozenset({"0.0.0.0", "::", "*"})

_ALLOW_PUBLIC_BIND_ENV = "WEBUI_ALLOW_PUBLIC_BIND"
_ALLOW_REMOTE_BIND_ENV = "WEBUI_ALLOW_REMOTE_BIND"

_FORBIDDEN_CLIENT_PATTERNS = (
    re.compile(r"GITEA_(?:TOKEN|PASS|PASSWORD)", re.I),
    re.compile(r"Bearer\s+[A-Za-z0-9._\-]{20,}"),
    re.compile(r"password\s*[:=]\s*['\"][^'\"]+['\"]", re.I),
)


@dataclass(frozen=True)
class BindAssessment:
    host: str
    disposition: Disposition
    message: str
    override_env: str | None = None


def _truthy_env(name: str) -> bool:
    return os.environ.get(name, "").strip().lower() in {"1", "true", "yes"}


def assess_bind_host(host: str) -> BindAssessment:
    """Classify a bind host as safe, warn, or refuse (fail closed on 0.0.0.0/::)."""
    normalized = (host or "").strip().lower()
    if not normalized:
        return BindAssessment(
            host=host,
            disposition="refuse",
            message="WEBUI_HOST must not be empty.",
        )

    if normalized in _LOOPBACK_HOSTS:
        return BindAssessment(
            host=host,
            disposition="safe",
            message="Loopback bind — suitable for local operator console.",
        )

    if normalized in _ALL_INTERFACE_HOSTS:
        if _truthy_env(_ALLOW_PUBLIC_BIND_ENV):
            return BindAssessment(
                host=host,
                disposition="warn",
                message=(
                    "Binding all interfaces. Protect with Cloudflare Access, "
                    "WARP, VPN, or equivalent before exposing beyond localhost."
                ),
                override_env=_ALLOW_PUBLIC_BIND_ENV,
            )
        return BindAssessment(
            host=host,
            disposition="refuse",
            message=(
                f"Refusing all-interface bind ({host!r}). Set "
                f"{_ALLOW_PUBLIC_BIND_ENV}=1 only when fronted by trusted "
                "network access controls."
            ),
            override_env=_ALLOW_PUBLIC_BIND_ENV,
        )

    try:
        addr = ipaddress.ip_address(normalized)
        if addr.is_loopback:
            return BindAssessment(
                host=host,
                disposition="safe",
                message="Loopback bind — suitable for local operator console.",
            )
    except ValueError:
        pass

    if _truthy_env(_ALLOW_REMOTE_BIND_ENV):
        return BindAssessment(
            host=host,
            disposition="warn",
            message=(
                "Non-loopback bind acknowledged. Restrict to a trusted network "
                "and add Cloudflare Access, WARP, or VPN if reachable beyond it."
            ),
            override_env=_ALLOW_REMOTE_BIND_ENV,
        )

    return BindAssessment(
        host=host,
        disposition="warn",
        message=(
            f"Non-loopback bind ({host!r}). MVP expects 127.0.0.1; set "
            f"{_ALLOW_REMOTE_BIND_ENV}=1 to acknowledge a trusted-network bind."
        ),
        override_env=_ALLOW_REMOTE_BIND_ENV,
    )


def runtime_assumptions() -> dict[str, str]:
    """Documented runtime paths and hosts — never includes secrets."""
    repo_root = (os.environ.get("WEBUI_REPO_ROOT") or "").strip()
    registry = (os.environ.get("WEBUI_PROJECT_REGISTRY") or "").strip()
    profile_config = (os.environ.get("GITEA_MCP_CONFIG") or "").strip()
    profile_name = (os.environ.get("GITEA_MCP_PROFILE") or "").strip()
    return {
        "deployment_mode": "internal-operator-console",
        "webui_host_env": "WEBUI_HOST",
        "webui_port_env": "WEBUI_PORT",
        "webui_repo_root_env": "WEBUI_REPO_ROOT",
        "webui_repo_root": repo_root or "(defaults to repository root)",
        "webui_project_registry_env": "WEBUI_PROJECT_REGISTRY",
        "webui_project_registry": registry or "(packaged webui/data/projects.registry.json)",
        "gitea_mcp_config_env": "GITEA_MCP_CONFIG",
        "gitea_mcp_config": profile_config or "(optional; server-side only)",
        "gitea_mcp_profile_env": "GITEA_MCP_PROFILE",
        "gitea_mcp_profile": profile_name or "(optional; server-side only)",
        "gitea_credentials": "Resolved server-side via gitea_auth; never embedded in HTML/JS",
        "public_bind_override_env": _ALLOW_PUBLIC_BIND_ENV,
        "remote_bind_override_env": _ALLOW_REMOTE_BIND_ENV,
    }


def scan_text_for_client_secrets(text: str) -> list[str]:
    """Return human-readable findings if *text* looks like it embeds secrets."""
    findings: list[str] = []
    for pattern in _FORBIDDEN_CLIENT_PATTERNS:
        if pattern.search(text):
            findings.append(f"matched forbidden client pattern: {pattern.pattern}")
    return findings


def deployment_snapshot(*, bind_host: str | None = None) -> dict[str, object]:
    host = bind_host if bind_host is not None else os.environ.get("WEBUI_HOST", "127.0.0.1")
    bind = assess_bind_host(host)
    return {
        "mode": "internal-operator-console",
        "mvp_auth": "none",
        "bind": asdict(bind),
        "runtime_assumptions": runtime_assumptions(),
        "client_secret_policy": (
            "No Gitea tokens, passwords, or profile secrets in HTML, JS, "
            "or browser storage."
        ),
    }