"""
Spark settings management for Microsoft Fabric workspaces.

Mutable settings object - read properties, set them, call ``.commit()``::

    settings = workspace.spark.settings
    settings.log = True
    settings.highconcurrency.interactive = True
    settings.pool.customcompute = True
    settings.pool.starter.nodes = 10
    settings.pool.starter.executors = 5
    settings.commit()
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Dict, Optional, Union

from ._shared.enums import PoolType

if TYPE_CHECKING:
    from ._client import FabricClient


# =============================================================================
# Sub-setting classes
# =============================================================================


@dataclass
class HighConcurrencySettings:
    """Shared Spark session configuration.

    Attributes:
        interactive: Share sessions across concurrent notebook interactive runs.
        pipelines:   Share sessions across concurrent pipeline Spark activity runs
                     (higher capacity SKUs only -- ``None`` when unsupported).
    """

    interactive: Optional[bool] = None
    pipelines: Optional[bool] = None

    @classmethod
    def fromJson(cls, data: Dict[str, Any]) -> "HighConcurrencySettings":
        return cls(
            interactive=data.get("notebookInteractiveRunEnabled"),
            pipelines=data.get("notebookPipelineRunEnabled"),
        )

    def toJson(self) -> Dict[str, Any]:
        result: Dict[str, Any] = {}
        if self.interactive is not None:
            result["notebookInteractiveRunEnabled"] = self.interactive
        if self.pipelines is not None:
            result["notebookPipelineRunEnabled"] = self.pipelines
        return result

    def __repr__(self) -> str:
        return f"HighConcurrencySettings(interactive={self.interactive!r}, pipelines={self.pipelines!r})"


@dataclass
class SparkEnvironment:
    """Default Spark environment for workspace jobs.

    Importable as ``Environment`` for concise usage::

        from fabias.fabric.spark import Environment
        workspace.spark.settings.environment = Environment("ML Env", "1.3")

    Attributes:
        name:    Environment display name (``None`` = no default environment).
        version: Fabric runtime version string, e.g. ``"1.3"``.
    """

    name: Optional[str] = None
    version: Optional[str] = None

    @classmethod
    def fromJson(cls, data: Dict[str, Any]) -> "SparkEnvironment":
        return cls(name=data.get("name"), version=data.get("runtimeVersion"))

    def toJson(self) -> Dict[str, Any]:
        result: Dict[str, Any] = {}
        if self.name is not None:
            result["name"] = self.name
        if self.version is not None:
            result["runtimeVersion"] = self.version
        return result

    def __repr__(self) -> str:
        return f"Environment(name={self.name!r}, version={self.version!r})"


# Convenience alias
Environment = SparkEnvironment


@dataclass
class JobSettings:
    """Spark job and session settings.

    Attributes:
        reservecores: ``True`` = conservative admission (limits concurrent jobs to avoid
                      over-committing cores). ``False`` = allow more concurrent jobs.
        timeout:      Idle session timeout in minutes before a session is released.
    """

    reservecores: Optional[bool] = None
    timeout: Optional[int] = None

    @classmethod
    def fromJson(cls, data: Dict[str, Any]) -> "JobSettings":
        return cls(
            reservecores=data.get("conservativeJobAdmissionEnabled"),
            timeout=data.get("sessionTimeoutInMinutes"),
        )

    def toJson(self) -> Dict[str, Any]:
        result: Dict[str, Any] = {}
        if self.reservecores is not None:
            result["conservativeJobAdmissionEnabled"] = self.reservecores
        if self.timeout is not None:
            result["sessionTimeoutInMinutes"] = self.timeout
        return result

    def __repr__(self) -> str:
        return f"JobSettings(reservecores={self.reservecores!r}, timeout={self.timeout!r})"


@dataclass
class DefaultPoolSettings:
    """Default pool selection for workspace Spark jobs.

    Attributes:
        name: Pool display name.
        type: Pool type -- :class:`~fabias.fabric.PoolType` enum or raw string.
    """

    name: Optional[str] = None
    type: Optional[Union[PoolType, str]] = None

    @classmethod
    def fromJson(cls, data: Dict[str, Any]) -> "DefaultPoolSettings":
        return cls(name=data.get("name"), type=data.get("type"))

    def toJson(self) -> Dict[str, Any]:
        result: Dict[str, Any] = {}
        if self.name is not None:
            result["name"] = self.name
        if self.type is not None:
            result["type"] = self.type.value if isinstance(self.type, PoolType) else self.type
        return result

    def __repr__(self) -> str:
        return f"DefaultPoolSettings(name={self.name!r}, type={self.type!r})"


@dataclass
class StarterPoolSettings:
    """Starter pool size constraints.

    Attributes:
        nodes:     Maximum node count.
        executors: Maximum executors per session.
    """

    nodes: Optional[int] = None
    executors: Optional[int] = None

    @classmethod
    def fromJson(cls, data: Dict[str, Any]) -> "StarterPoolSettings":
        return cls(nodes=data.get("maxNodeCount"), executors=data.get("maxExecutors"))

    def toJson(self) -> Dict[str, Any]:
        result: Dict[str, Any] = {}
        if self.nodes is not None:
            result["maxNodeCount"] = self.nodes
        if self.executors is not None:
            result["maxExecutors"] = self.executors
        return result

    def __repr__(self) -> str:
        return f"StarterPoolSettings(nodes={self.nodes!r}, executors={self.executors!r})"


@dataclass
class PoolSettings:
    """Workspace pool configuration.

    Attributes:
        customcompute: Whether users can customise compute per session.
        default:       Default pool -- name and type.
        starter:       Starter pool constraints -- max nodes and executors.
    """

    customcompute: Optional[bool] = None
    default: DefaultPoolSettings = field(default_factory=DefaultPoolSettings)
    starter: StarterPoolSettings = field(default_factory=StarterPoolSettings)

    @classmethod
    def fromJson(cls, data: Dict[str, Any]) -> "PoolSettings":
        return cls(
            customcompute=data.get("customizeComputeEnabled"),
            default=DefaultPoolSettings.fromJson(data.get("defaultPool", {})),
            starter=StarterPoolSettings.fromJson(data.get("starterPool", {})),
        )

    def toJson(self) -> Dict[str, Any]:
        result: Dict[str, Any] = {}
        if self.customcompute is not None:
            result["customizeComputeEnabled"] = self.customcompute
        default_json = self.default.toJson()
        if default_json:
            result["defaultPool"] = default_json
        starter_json = self.starter.toJson()
        if starter_json:
            result["starterPool"] = starter_json
        return result

    def __repr__(self) -> str:
        return (
            f"PoolSettings(customcompute={self.customcompute!r}, "
            f"default={self.default!r}, starter={self.starter!r})"
        )


# =============================================================================
# Settings container
# =============================================================================


class SparkSettings:
    """Mutable container for workspace Spark settings.

    Fetch via ``workspace.spark.settings`` (lazy-loaded and cached). Mutate
    properties in-place, then call :meth:`commit` to push all changes.

    Attributes:
        log:             Enable automatic Spark run logging.
        highconcurrency: Shared session settings.
        environment:     Default environment -- name and runtime version.
        jobs:            Job admission control -- reserve cores and queue timeout.
        pool:            Pool config -- default pool, starter pool, custom compute.

    Examples::

        s = workspace.spark.settings
        s.log = True
        s.highconcurrency.interactive = True
        s.environment = Environment("ML Env", "1.3")
        s.pool.customcompute = True
        s.pool.starter.nodes = 10
        s.pool.starter.executors = 5
        s.commit()
    """

    def __init__(self, client: "FabricClient", workspace_id: str, data: Dict[str, Any]):
        self._client = client
        self._workspace_id = workspace_id
        self._endpoint = f"/workspaces/{workspace_id}/spark/settings"
        self._hydrate(data)

    def _hydrate(self, data: Dict[str, Any]) -> None:
        self._raw = data  # preserve for debugging
        self.log: Optional[bool] = data.get("automaticLog", {}).get("enabled")
        self.highconcurrency: HighConcurrencySettings = HighConcurrencySettings.fromJson(
            data.get("highConcurrency", {})
        )
        self.environment: SparkEnvironment = SparkEnvironment.fromJson(data.get("environment", {}))
        self.jobs: JobSettings = JobSettings.fromJson(data.get("job", {}))
        self.pool: PoolSettings = PoolSettings.fromJson(data.get("pool", {}))

    def _toJson(self) -> Dict[str, Any]:
        payload: Dict[str, Any] = {}
        if self.log is not None:
            payload["automaticLog"] = {"enabled": self.log}
        hc = self.highconcurrency.toJson()
        if hc:
            payload["highConcurrency"] = hc
        env = self.environment.toJson()
        if env:
            payload["environment"] = env
        jobs = self.jobs.toJson()
        if jobs:
            payload["job"] = jobs
        pool = self.pool.toJson()
        if pool:
            payload["pool"] = pool
        return payload

    def commit(self) -> "SparkSettings":
        """Push all accumulated changes to the API, then re-sync from server.

        Returns:
            self -- for optional chaining/inspection.
        """
        self._client.patch(self._endpoint, data=self._toJson())
        response = self._client.get(self._endpoint)
        self._hydrate(response.json())
        return self

    def __repr__(self) -> str:
        return (
            f"SparkSettings("
            f"log={self.log!r}, "
            f"environment={self.environment!r}, "
            f"pool_default={self.pool.default.name!r})"
        )


# =============================================================================
# Handler (property on Workspace)
# =============================================================================


class Spark:
    """Manages Spark settings for a Microsoft Fabric workspace.

    Accessed via ``workspace.spark``. The ``settings`` property lazy-loads
    and caches the :class:`SparkSettings` object.

    Examples::

        workspace.spark.settings.log = True
        workspace.spark.settings.pool.customcompute = True
        workspace.spark.settings.commit()
    """

    def __init__(self, client: "FabricClient", workspace_id: str):
        self._client = client
        self._workspace_id = workspace_id
        self._settings_cache: Optional[SparkSettings] = None

    @property
    def settings(self) -> SparkSettings:
        """Lazy-load and return the cached :class:`SparkSettings`."""
        if self._settings_cache is None:
            endpoint = f"/workspaces/{self._workspace_id}/spark/settings"
            response = self._client.get(endpoint)
            self._settings_cache = SparkSettings(self._client, self._workspace_id, response.json())
        return self._settings_cache
