"""Serve-layer authentication, permissions, and workspace scoping."""
from __future__ import annotations

from dataclasses import dataclass
import json
import os
from typing import Any

from logos_adk.rbac import default_rbac_policy, expand_roles


WORKSPACE_HEADER = "X-Workspace-Id"


@dataclass
class ServePrincipal:
    """Authenticated API principal."""

    name: str
    scopes: set[str]
    workspaces: set[str]
    roles: set[str] | None = None

    def has_scope(self, scope: str) -> bool:
        return "*" in self.scopes or scope in self.scopes

    def allows_workspace(self, workspace_id: str | None) -> bool:
        if "*" in self.workspaces:
            return True
        if workspace_id is None:
            return False
        return workspace_id in self.workspaces


def _normalize_principal(token_name: str, raw: dict[str, Any] | None) -> ServePrincipal:
    data = raw or {}
    role_scopes = expand_roles(data, default_rbac_policy())
    scopes = set(data.get("scopes") or [])
    scopes.update(role_scopes)
    if not scopes:
        scopes = {"*"}
    workspaces = set(data.get("workspaces") or ["*"])
    roles = set(data.get("roles") or ([] if data.get("role") is None else [data["role"]]))
    return ServePrincipal(name=token_name, scopes=scopes, workspaces=workspaces, roles=roles or None)


def load_api_keys_config(
    *,
    api_key: str | None = None,
    api_keys: dict[str, dict[str, Any]] | None = None,
) -> dict[str, ServePrincipal]:
    """Build a token registry from explicit args and environment."""
    registry: dict[str, ServePrincipal] = {}
    if api_keys:
        for token, config in api_keys.items():
            registry[token] = _normalize_principal(config.get("name", "api-client"), config)
    env_json = os.getenv("LOGOS_ADK_API_KEYS_JSON")
    if env_json:
        payload = json.loads(env_json)
        if isinstance(payload, dict):
            for token, config in payload.items():
                if isinstance(config, dict):
                    registry[token] = _normalize_principal(config.get("name", "env-client"), config)
    if api_key and api_key not in registry:
        registry[api_key] = ServePrincipal(name="legacy-api-key", scopes={"*"}, workspaces={"*"})
    return registry


def required_scope_for_request(path: str, method: str) -> str | None:
    """Resolve the minimum scope required for a request path."""
    if path.startswith("/health"):
        return None
    if path.startswith("/learning/prompts") and method == "GET":
        return "prompt:read"
    if path.startswith("/learning/prompts") and method == "POST":
        return "prompt:write"
    if path.startswith("/learning/recommendations") and method == "GET":
        return "learning:read"
    if path.startswith("/learning/recommendations") and method == "POST":
        return "learning:write"
    if path.startswith("/learning/improvement") and method == "POST":
        return "learning:write"
    if path.startswith("/learning/improvement") and method == "GET":
        return "learning:read"
    if path.startswith("/learning/retrieval") and method == "POST":
        return "learning:write"
    if path.startswith("/learning/retrieval") and method == "GET":
        return "learning:read"
    if path.startswith("/learning/datasets"):
        return "learning:read"
    if path.startswith("/feedback") or path.startswith("/outcomes"):
        return "learning:write"
    if path.startswith("/learning"):
        return "learning:read"
    if "/quality/" in path and method == "POST":
        return "quality:run"
    if "/quality/" in path and method == "GET":
        return "quality:read"
    if path.endswith("/run") or path.endswith("/batch") or path.endswith("/stream"):
        return "agent:run"
    if path.endswith("/ingest"):
        return "knowledge:write"
    if path.endswith("/state/versions") or path.endswith("/state/export") or path.endswith("/state/shards"):
        return "state:read"
    if path.endswith("/state/import") or path.endswith("/state/rollback"):
        return "state:write"
    if path.startswith("/artifacts") and method == "GET":
        return "state:read"
    if path.startswith("/observability") or path.startswith("/metrics"):
        return "observability:read"
    return "agent:run"


def resolve_workspace_id(headers: Any, query_params: Any) -> str | None:
    """Resolve workspace id from headers or query params."""
    header_value = headers.get(WORKSPACE_HEADER)
    if header_value:
        return str(header_value)
    query_value = query_params.get("workspace_id")
    if query_value:
        return str(query_value)
    return None


def scope_session_id(session_id: str | None, workspace_id: str | None) -> str | None:
    """Prefix a session id with workspace scope."""
    if session_id is None or workspace_id is None:
        return session_id
    if session_id.startswith(f"{workspace_id}:"):
        return session_id
    return f"{workspace_id}:{session_id}"
