r"""Enterprise provisioning: machine-wide managed configuration + lock.

An admin can drop a JSON policy file so the app ships pre-configured (server
URL, key, TLS, enhancement source) and can LOCK those fields so users cannot
change them. This is the standard managed-configuration pattern (like Chrome/
VS Code policies).

File (highest precedence, read after the user's settings.json):
  Windows: %PROGRAMDATA%\VoxCast\managed.json
  POSIX:   /etc/voxcast/managed.json   (override via VOXCAST_MANAGED_CONFIG)

Shape:
  {
    "values":  { "remote_enabled": true, "remote_url": "https://srv:8590",
                 "enhance_source": "server", "remote_tls_mode": "pinned" },
    "secrets": { "remote_server_api_key": "vxc_..." },
    "locked":  ["remote_enabled", "remote_url", "remote_server_api_key",
                "enhance_source"]
  }

`values` overlay the user settings on every load (so a locked field can never
drift). `secrets` are written into the OS credential store once. `locked` names
are exposed to the UI, which disables those controls. Security note for admins:
managed.json can hold the key in clear text; set its ACL to admins-only.
"""
from __future__ import annotations

import json
import os
from pathlib import Path

from loguru import logger

# Only these keys may be provisioned/locked (allow-list: a policy file must not
# be able to set arbitrary internal fields).
ALLOWED_VALUE_KEYS = frozenset({
    "remote_enabled", "remote_url", "remote_tls_mode", "enhance_source",
    "llm_enabled", "llm_provider", "ui_language",
})
# Secret name in the file -> credential-store key.
SECRET_KEYS = {
    "remote_server_api_key": "remote_server",
    "llm_anthropic_api_key": "llm_anthropic",
    "llm_openai_api_key": "llm_openai",
}


def managed_path() -> Path:
    explicit = os.environ.get("VOXCAST_MANAGED_CONFIG")
    if explicit:
        return Path(explicit)
    if os.name == "nt":
        base = os.environ.get("PROGRAMDATA", r"C:\ProgramData")
        return Path(base) / "VoxCast" / "managed.json"
    return Path("/etc/voxcast/managed.json")


def load_managed(path: Path | None = None) -> dict:
    """Parse the managed policy. Returns {values, secrets, locked} with safe
    defaults; never raises (a broken policy must not brick the app)."""
    path = path or managed_path()
    empty = {"values": {}, "secrets": {}, "locked": []}
    if not path.exists():
        return empty
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except (ValueError, OSError) as e:
        logger.warning("managed.json unreadable ({}); ignoring", e)
        return empty
    if not isinstance(data, dict):
        return empty

    raw_values = data.get("values")
    values = {k: v for k, v in raw_values.items() if k in ALLOWED_VALUE_KEYS} \
        if isinstance(raw_values, dict) else {}

    raw_secrets = data.get("secrets")
    secrets = {k: v for k, v in raw_secrets.items()
               if k in SECRET_KEYS and isinstance(v, str) and v} \
        if isinstance(raw_secrets, dict) else {}

    raw_locked = data.get("locked")
    locked = [k for k in raw_locked
              if isinstance(k, str)
              and (k in ALLOWED_VALUE_KEYS or k in SECRET_KEYS)] \
        if isinstance(raw_locked, list) else []

    if values or secrets or locked:
        logger.info("Managed policy loaded: {} value(s), {} secret(s), "
                    "{} locked", len(values), len(secrets), len(locked))
    return {"values": values, "secrets": secrets, "locked": locked}


def apply_secrets(secrets: dict) -> None:
    """Write provisioned secrets into the OS credential store (idempotent)."""
    if not secrets:
        return
    from . import credentials
    for file_key, value in secrets.items():
        cred_key = SECRET_KEYS.get(file_key)
        if not cred_key:
            continue
        try:
            if credentials.get_secret(cred_key) != value:
                credentials.set_secret(cred_key, value)
        except Exception as e:  # noqa: BLE001
            logger.warning("Could not apply managed secret {}: {}", cred_key, e)
