"""Remote PlatformContext for plugins running in subprocess isolation.

When a plugin runs as a separate process (subprocess isolation mode),
it can't access platform repositories directly.  This module provides
a ``RemotePlatformContext`` that implements the ``PlatformContext`` ABC
by making HTTP calls to the platform's internal API.

Auto-detection:
    ``create_context_for_environment()`` checks for the ``MLD_PLATFORM_URL``
    env var and returns a ``RemotePlatformContext`` if present, or ``None``
    if the plugin is running standalone.
"""

import os
from contextlib import asynccontextmanager
from typing import Any, AsyncGenerator, Callable, Optional

from fastapi import Depends, HTTPException, Request

from mld_sdk.context import PlatformContext
from mld_sdk.repositories import (
    Experiment,
    PlatformConfig,
    PluginRoleRepository,
    User,
)


def create_context_for_environment() -> Optional[PlatformContext]:
    """Auto-detect running mode and return the appropriate context.

    Returns a ``RemotePlatformContext`` if ``MLD_PLATFORM_URL`` is set
    (subprocess mode), or ``None`` for standalone operation.
    """
    if os.environ.get("MLD_PLATFORM_URL"):
        return RemotePlatformContext()
    return None


class RemotePlatformContext(PlatformContext):
    """PlatformContext that calls back to the platform over HTTP.

    Used by plugins running in isolated subprocesses.  All repository
    methods delegate to the platform's ``/api/internal/`` endpoints.
    """

    def __init__(self):
        import httpx

        self._platform_url = os.environ["MLD_PLATFORM_URL"]
        self._plugin_token = os.environ.get("MLD_PLUGIN_TOKEN", "")
        self._plugin_name = os.environ.get("MLD_PLUGIN_NAME", "")
        self._client = httpx.AsyncClient(
            base_url=f"{self._platform_url}/api/internal",
            headers={
                "X-MLD-Internal-Token": self._plugin_token,
                "X-MLD-Plugin-Name": self._plugin_name,
            },
            timeout=10.0,
        )

    @property
    def is_authenticated(self) -> bool:
        return False  # Auth state is per-request, handled via headers

    def get_current_user_dependency(self) -> Callable:
        """Return a FastAPI dependency that reads user identity from proxy headers."""

        async def _get_user_from_headers(request: Request) -> dict:
            user_id = request.headers.get("x-mld-user-id")
            username = request.headers.get("x-mld-username")
            role = request.headers.get("x-mld-user-role")
            if user_id:
                return {"sub": user_id, "username": username, "role": role}
            raise HTTPException(status_code=401, detail="Not authenticated")

        return _get_user_from_headers

    def get_optional_user_dependency(self) -> Callable:
        """Return a FastAPI dependency that reads user identity, or None."""

        async def _get_optional_user(request: Request) -> Optional[dict]:
            user_id = request.headers.get("x-mld-user-id")
            if user_id:
                return {
                    "sub": user_id,
                    "username": request.headers.get("x-mld-username"),
                    "role": request.headers.get("x-mld-user-role"),
                }
            return None

        return _get_optional_user

    def get_user_repository(self) -> Optional["_RemoteUserRepository"]:
        return _RemoteUserRepository(self._client)

    def get_experiment_repository(self) -> Optional["_RemoteExperimentRepository"]:
        return _RemoteExperimentRepository(self._client)

    def get_plugin_data_repository(self) -> Optional["_RemotePluginDataRepository"]:
        return _RemotePluginDataRepository(self._client)

    def get_plugin_role_repository(self) -> Optional[PluginRoleRepository]:
        return None  # Not yet supported in subprocess mode

    def require_plugin_role(self, *allowed_roles: str) -> Any:
        """Fail closed: plugin roles are not yet supported in subprocess mode.

        Platform admins are allowed through (same as in-process).
        All other users are blocked to prevent privilege escalation.
        """

        async def _check_role(request: Request) -> dict:
            user_id = request.headers.get("x-mld-user-id")
            role = request.headers.get("x-mld-user-role", "")
            if not user_id:
                raise HTTPException(status_code=401, detail="Not authenticated")
            if role == "admin":
                return {
                    "sub": user_id,
                    "username": request.headers.get("x-mld-username"),
                    "role": role,
                    "plugin_role": "admin",
                }
            raise HTTPException(
                status_code=403,
                detail="Plugin role checks are not available in subprocess isolation mode. Only platform admins can access role-protected routes.",
            )

        return Depends(_check_role)

    def get_config(self) -> PlatformConfig:
        return {}

    @asynccontextmanager
    async def get_shared_db_session(self) -> AsyncGenerator[Any, None]:
        raise NotImplementedError(
            "Shared database sessions are not available in subprocess isolation mode. "
            "Plugins requiring shared_database should run in-process."
        )


# ------------------------------------------------------------------
# Remote repository adapters
# ------------------------------------------------------------------


class _RemoteExperimentRepository:
    """HTTP-backed experiment repository for isolated plugins."""

    def __init__(self, client):
        self._client = client

    async def get_by_id(self, experiment_id: int) -> Optional[Experiment]:
        resp = await self._client.get(f"/experiments/{experiment_id}")
        if resp.status_code == 404:
            return None
        resp.raise_for_status()
        return Experiment(**resp.json())

    async def list_all(
        self,
        skip: int = 0,
        limit: int = 100,
        status: Optional[str] = None,
        experiment_type: Optional[str] = None,
        project: Optional[str] = None,
        created_by: Optional[int] = None,
        parent_experiment_id: Optional[int] = None,
        search: Optional[str] = None,
    ) -> tuple[list[Experiment], int]:
        params = {"skip": skip, "limit": limit}
        if status:
            params["status"] = status
        if experiment_type:
            params["experiment_type"] = experiment_type
        if project:
            params["project"] = project
        resp = await self._client.get("/experiments", params=params)
        resp.raise_for_status()
        data = resp.json()
        return [Experiment(**e) for e in data["experiments"]], data["total"]

    async def create(
        self,
        name: str,
        experiment_type: str,
        created_by: Optional[int] = None,
        parent_experiment_id: Optional[int] = None,
        project: Optional[str] = None,
        notes: Optional[str] = None,
        tags: Optional[dict] = None,
    ) -> Experiment:
        body: dict = {"name": name, "experiment_type": experiment_type}
        if created_by is not None:
            body["created_by"] = created_by
        if parent_experiment_id is not None:
            body["parent_experiment_id"] = parent_experiment_id
        if project:
            body["project"] = project
        if notes:
            body["notes"] = notes
        if tags:
            body["tags"] = tags
        resp = await self._client.post("/experiments", json=body)
        resp.raise_for_status()
        return Experiment(**resp.json())

    async def update(
        self,
        experiment_id: int,
        *,
        name: Optional[str] = None,
        status: Optional[str] = None,
        experiment_type: Optional[str] = None,
        parent_experiment_id: Optional[int] = None,
        project: Optional[str] = None,
        notes: Optional[str] = None,
        tags: Optional[dict] = None,
    ) -> Optional[Experiment]:
        body = {
            k: v
            for k, v in {
                "name": name,
                "status": status,
                "experiment_type": experiment_type,
                "parent_experiment_id": parent_experiment_id,
                "project": project,
                "notes": notes,
                "tags": tags,
            }.items()
            if v is not None
        }
        resp = await self._client.patch(f"/experiments/{experiment_id}", json=body)
        if resp.status_code == 404:
            return None
        resp.raise_for_status()
        return Experiment(**resp.json())

    async def delete(self, experiment_id: int) -> bool:
        resp = await self._client.delete(f"/experiments/{experiment_id}")
        return resp.status_code == 200

    async def has_design_data(self, experiment_id: int) -> bool:
        resp = await self._client.get(f"/experiments/{experiment_id}/data")
        return resp.status_code == 200


class _RemotePluginDataRepository:
    """HTTP-backed plugin data repository for isolated plugins."""

    def __init__(self, client):
        self._client = client

    async def save_experiment_data(self, experiment_id, plugin_id, data, schema_version="1.0"):
        resp = await self._client.put(
            f"/experiments/{experiment_id}/data",
            json={"plugin_id": plugin_id, "data": data, "schema_version": schema_version},
        )
        resp.raise_for_status()
        return resp.json()

    async def get_experiment_data(self, experiment_id):
        resp = await self._client.get(f"/experiments/{experiment_id}/data")
        if resp.status_code == 404:
            return None
        resp.raise_for_status()
        return resp.json()

    async def delete_experiment_data(self, experiment_id):
        resp = await self._client.delete(f"/experiments/{experiment_id}/data")
        return resp.status_code == 200

    async def save_analysis_result(self, experiment_id, plugin_id, result):
        resp = await self._client.put(
            f"/experiments/{experiment_id}/results/{plugin_id}",
            json={"plugin_id": plugin_id, "result": result},
        )
        resp.raise_for_status()
        return resp.json()

    async def get_analysis_result(self, experiment_id, plugin_id):
        resp = await self._client.get(f"/experiments/{experiment_id}/results/{plugin_id}")
        if resp.status_code == 404:
            return None
        resp.raise_for_status()
        return resp.json()

    async def get_analysis_results(self, experiment_id):
        resp = await self._client.get(f"/experiments/{experiment_id}/results")
        resp.raise_for_status()
        return resp.json()

    async def delete_analysis_result(self, experiment_id, plugin_id):
        resp = await self._client.delete(f"/experiments/{experiment_id}/results/{plugin_id}")
        return resp.status_code == 200


class _RemoteUserRepository:
    """HTTP-backed user repository for isolated plugins."""

    def __init__(self, client):
        self._client = client

    async def get_by_id(self, user_id: int) -> Optional[User]:
        resp = await self._client.get(f"/users/{user_id}")
        if resp.status_code == 404:
            return None
        resp.raise_for_status()
        return User(**resp.json())

    async def get_by_username(self, username: str) -> Optional[User]:
        resp = await self._client.get(f"/users/by-username/{username}")
        if resp.status_code == 404:
            return None
        resp.raise_for_status()
        return User(**resp.json())

    async def list_all(self, skip: int = 0, limit: int = 100) -> list[User]:
        resp = await self._client.get("/users", params={"skip": skip, "limit": limit})
        resp.raise_for_status()
        return [User(**u) for u in resp.json()]
