#!/opt/octava-webui/venv/bin/python
"""Root side of the versioned, fixed-operation Web UI privilege boundary."""

from __future__ import annotations

import asyncio
import inspect
import json
import os
import pwd
import subprocess
import sys
import syslog
from typing import Any


MAX_REQUEST_BYTES = 1024 * 1024
BACKEND_PATH = "/opt/octava-webui/backend"

OPERATIONS = {
    "network.interfaces.get": ("api.network", "get_interfaces"),
    "network.bridge.get": ("api.network", "get_bridge"),
    "network.bridge.set": ("api.network", "set_bridge"),
    "network.port.get": ("api.network", "get_port_config"),
    "network.port.configure": ("api.network", "configure_port"),
    "network.wan.get": ("api.network", "get_wan_status"),
    "network.vlans.get": ("api.network", "get_vlans"),
    "network.vlan.create": ("api.network", "create_vlan"),
    "network.vlan.delete": ("api.network", "delete_vlan"),
    "network.hardware.get": ("api.network", "get_hardware_config"),
    "network.hardware.set": ("api.network", "set_hardware_config"),
    "network.port.restart": ("api.network", "restart_port"),
    "vpn.tunnels.get": ("api.vpn", "list_tunnels"),
    "vpn.tunnel.create": ("api.vpn", "create_tunnel"),
    "vpn.tunnel.toggle": ("api.vpn", "toggle_tunnel"),
    "vpn.tunnel.delete": ("api.vpn", "delete_tunnel"),
    "vpn.tunnel.config.get": ("api.vpn", "get_tunnel_config"),
    "vpn.tunnel.edit": ("api.vpn", "edit_tunnel"),
    "vpn.tunnel.system_role": ("api.vpn", "set_tunnel_system_role"),
    "openvpn.status.get": ("api.openvpn", "get_status"),
    "openvpn.pki.init": ("api.openvpn", "init_pki"),
    "openvpn.server.configure": ("api.openvpn", "configure_server"),
    "openvpn.server.start": ("api.openvpn", "start_server"),
    "openvpn.server.stop": ("api.openvpn", "stop_server"),
    "openvpn.server.enable": ("api.openvpn", "enable_server"),
    "openvpn.server.disable": ("api.openvpn", "disable_server"),
    "openvpn.clients.get": ("api.openvpn", "list_clients"),
    "openvpn.client.create": ("api.openvpn", "create_client"),
    "openvpn.client.bundle.get": ("api.openvpn", "get_client_ovpn"),
    "openvpn.client.delete": ("api.openvpn", "delete_client"),
    "system.time.set": ("api.system", "set_time_settings"),
    "system.hostname.set": ("api.system", "set_hostname"),
    "system.disk.expand": ("api.system", "expand_disk"),
    "routing.isolated.apply": ("api.routing", "apply_isolated_routing"),
    "secure_dns.desired.save": ("api.secure_dns", "save_secure_dns_desired"),
    "secure_dns.profiles.save": ("api.secure_dns", "save_dns_profiles"),
    "secure_dns.policy.save": ("api.secure_dns", "save_secure_dns_policy"),
    "secure_dns.policy.apply": ("api.secure_dns", "apply_secure_dns_policy"),
    "secure_dns.egress.control": ("api.secure_dns", "set_dns_egress_control"),
    "dpi.readiness.check": ("api.dpi", "check_dpi"),
}


class HelperError(Exception):
    def __init__(self, status: int, detail: str):
        super().__init__(detail)
        self.status = status
        self.detail = detail


class RequestBody:
    method = "POST"

    def __init__(self, body: Any):
        self._body = body

    async def json(self) -> Any:
        return self._body


def audit(operation: str, outcome: str) -> None:
    syslog.openlog("octava-control", facility=syslog.LOG_AUTH)
    syslog.syslog(syslog.LOG_INFO, f"caller=octava operation={operation} outcome={outcome}")


def verify_caller() -> None:
    if os.geteuid() != 0:
        raise HelperError(403, "Helper must execute as root")
    try:
        octava_uid = pwd.getpwnam("octava").pw_uid
    except KeyError as exc:
        raise HelperError(503, "Service identity is not installed") from exc
    if os.environ.get("SUDO_USER") != "octava" or os.environ.get("SUDO_UID") != str(octava_uid):
        raise HelperError(403, "Invalid privileged helper caller")


def authenticate_root(payload: dict[str, Any]) -> bool:
    if set(payload) != {"username", "password"} or payload.get("username") != "root":
        raise HelperError(400, "Invalid authentication request")
    password = payload.get("password")
    if not isinstance(password, str) or len(password) > 1024:
        raise HelperError(400, "Invalid authentication request")
    import pam

    return bool(pam.pam().authenticate("root", password, service="login"))


def change_root_password(payload: dict[str, Any]) -> dict[str, Any]:
    if set(payload) != {"password"}:
        raise HelperError(400, "Invalid password request")
    password = payload.get("password")
    if not isinstance(password, str) or len(password) < 8 or len(password) > 1024 or "\n" in password:
        raise HelperError(400, "Password does not meet policy")
    proc = subprocess.run(
        ["/usr/sbin/chpasswd"],
        input=f"root:{password}",
        text=True,
        capture_output=True,
        timeout=15,
        check=False,
        env={"PATH": "/usr/sbin:/usr/bin:/sbin:/bin"},
    )
    if proc.returncode != 0:
        raise HelperError(500, "Failed to change system password")
    return {"success": True, "message": "Root password changed"}


async def invoke_endpoint(operation: str, payload: dict[str, Any]) -> Any:
    os.environ["OCTAVA_CONTROL_HELPER_CONTEXT"] = "1"
    module_name, function_name = OPERATIONS[operation]
    if BACKEND_PATH not in sys.path:
        sys.path.insert(0, BACKEND_PATH)
    module = __import__(module_name, fromlist=[function_name])
    function = getattr(module, function_name)
    signature = inspect.signature(function)
    kwargs: dict[str, Any] = {}
    unexpected = set(payload) - set(signature.parameters) - {"request_body"}
    if unexpected:
        raise HelperError(400, "Unexpected privileged operation fields")

    for name, parameter in signature.parameters.items():
        if name == "request":
            kwargs[name] = RequestBody(payload.get("request_body", {}))
        elif name == "user":
            kwargs[name] = {"sub": "root", "role": "root"}
        elif name in payload:
            value = payload[name]
            annotation = parameter.annotation
            if inspect.isclass(annotation) and hasattr(annotation, "model_validate"):
                value = annotation.model_validate(value)
            kwargs[name] = value
        elif parameter.default is inspect.Parameter.empty:
            raise HelperError(400, f"Missing privileged operation field: {name}")
    return await function(**kwargs)


async def dispatch(operation: str, payload: dict[str, Any]) -> Any:
    if operation == "health.ping":
        if payload:
            raise HelperError(400, "Health request must be empty")
        return {"status": "ok", "protocol": 1}
    if operation == "auth.pam.root":
        return {"authenticated": authenticate_root(payload)}
    if operation == "auth.password.root.change":
        return change_root_password(payload)
    if operation not in OPERATIONS:
        raise HelperError(404, "Unknown privileged operation")
    return await invoke_endpoint(operation, payload)


def emit(response: dict[str, Any]) -> None:
    sys.stdout.write(json.dumps(response, separators=(",", ":"), default=str))
    sys.stdout.write("\n")


def main() -> int:
    operation = "invalid"
    try:
        verify_caller()
        raw = sys.stdin.buffer.read(MAX_REQUEST_BYTES + 1)
        if len(raw) > MAX_REQUEST_BYTES:
            raise HelperError(413, "Privileged request too large")
        envelope = json.loads(raw)
        if not isinstance(envelope, dict) or set(envelope) != {"version", "operation", "payload"}:
            raise HelperError(400, "Invalid privileged request envelope")
        if envelope["version"] != 1 or not isinstance(envelope["operation"], str):
            raise HelperError(400, "Unsupported privileged protocol")
        if not isinstance(envelope["payload"], dict):
            raise HelperError(400, "Invalid privileged payload")
        operation = envelope["operation"]
        result = asyncio.run(dispatch(operation, envelope["payload"]))
        audit(operation, "success")
        emit({"ok": True, "result": result})
        return 0
    except HelperError as exc:
        audit(operation, "denied")
        emit({"ok": False, "status": exc.status, "detail": exc.detail})
        return 1
    except json.JSONDecodeError:
        audit(operation, "denied")
        emit({"ok": False, "status": 400, "detail": "Invalid privileged JSON"})
        return 1
    except Exception as exc:
        try:
            from fastapi import HTTPException
            if isinstance(exc, HTTPException):
                audit(operation, "failed")
                emit({"ok": False, "status": exc.status_code, "detail": str(exc.detail)})
                return 1
        except ImportError:
            pass
        audit(operation, "failed")
        emit({"ok": False, "status": 500, "detail": "Privileged operation failed"})
        return 1


if __name__ == "__main__":
    raise SystemExit(main())
