"""
Fabric Environment management.

Provides classes for managing Fabric Spark environments, including
publishing and library management.
"""

from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union, cast, overload

from ..._shared.exceptions import FabiasError
from ..._shared.runtime import runtime
from ..._shared.utils import GUID
from ._shared import ItemAccessor
from .libraries import Libraries

if TYPE_CHECKING:
    from .._client import FabricClient


class EnvironmentAccessor(ItemAccessor):
    """Accessor for environment operations."""

    ITEM_TYPE = "Environment"
    ITEM_CLASS: Optional[type] = None  # Set after Environment definition

    @overload
    def __call__(self, identifier: str) -> "Environment": ...

    @overload
    def __call__(self, identifier: None = None) -> List["Environment"]: ...

    def __call__(
        self, identifier: Optional[str] = None
    ) -> Union["Environment", List["Environment"]]:
        """Get environment by name/ID or list all environments."""
        result = super().__call__(identifier)
        if isinstance(result, list):
            return cast(List["Environment"], result)
        return cast("Environment", result)

    def add(
        self, name: str, description: Optional[str] = None, folder_id: Optional[str] = None
    ) -> "Environment":
        """Create new environment."""
        return cast("Environment", super().add(name, description, folder_id))


class Environment:
    """
    Represents a Microsoft Fabric Environment with full management capabilities.

    Provides methods to:
    - Check environment status and publish state
    - Publish pending changes
    - Cancel publishing
    - Manage custom libraries

    Attributes:
        id (str): Environment GUID
        name (str): Environment display name
        publish_state (str): Current publish state (Running, Success, Failed, etc.)

    Examples:
        >>> env = workspace.environment("ML Environment")
        >>>
        >>> # Check publish state
        >>> env.refresh()
        >>> if env.is_publishing:
        ...     print("Publishing in progress...")
        >>>
        >>> # Manage libraries
        >>> libs = env.libraries
        >>> await libs.upload("my_package.whl")
        >>> env.publish()
    """

    PUBLISH_STATES_ACTIVE = {"Running", "Waiting", "Cancelling"}

    def __init__(
        self,
        client: "FabricClient",
        workspace_id: str,
        identifier: Optional[str] = None,
        item_data: Optional[Dict[str, Any]] = None,
    ):
        """
        Initialize Environment with resolution or pre-populated data.

        Args:
            client: Authenticated FabricClient
            workspace_id: Workspace GUID
            identifier: Environment display name or GUID (optional if item_data provided)
            item_data: Pre-populated item data from API (skips resolution)
        """
        self._client = client
        self._workspace_id = workspace_id
        self.id: Optional[str] = None
        self.name: Optional[str] = None
        self.publish_state: Optional[str] = None

        self._libraries: Optional[Libraries] = None

        if item_data:
            # Pre-populate from provided data
            self.id = item_data.get("id")
            self.name = item_data.get("displayName")
            # Don't call refresh() for pre-populated items from list
        elif identifier:
            # Resolve via API or sempy
            self._resolve_environment(identifier)
            self.refresh()
        else:
            raise ValueError("Either identifier or item_data must be provided")

    def _resolve_environment(self, identifier: str) -> None:
        """Resolve environment identifier."""
        # Try sempy.fabric first
        if runtime.isFabric:
            sempy = runtime.sempy
            if sempy:
                try:
                    items = sempy.list_items(workspace=self._workspace_id, type="Environment")

                    if GUID.valid(identifier):
                        row = items[items["Id"] == identifier]
                    else:
                        row = items[items["Display Name"] == identifier]

                    if not row.empty:
                        self.id = row.iloc[0]["Id"]
                        self.name = row.iloc[0].get("Display Name")
                        return
                except Exception:
                    pass

        # Use REST API
        endpoint = f"/workspaces/{self._workspace_id}/items?type=Environment"
        response = self._client.get(endpoint)
        items = response.json().get("value", [])

        matches = [
            item
            for item in items
            if item.get("displayName") == identifier or item.get("id") == identifier
        ]

        if not matches:
            raise FabiasError(f"Environment '{identifier}' not found")

        self.id = matches[0].get("id")
        self.name = matches[0].get("displayName")

    @property
    def _endpoint(self) -> str:
        """Get the API endpoint for this environment."""
        return f"/workspaces/{self._workspace_id}/environments/{self.id}"

    def refresh(self) -> "Environment":
        """
        Refresh environment data from API.

        Returns:
            Environment: Self for method chaining
        """
        response = self._client.get(self._endpoint)
        data = response.json()

        self.name = data.get("displayName")

        properties = data.get("properties", {})
        publish_details = properties.get("publishDetails", {})
        self.publish_state = publish_details.get("state")

        return self

    @property
    def isPublishing(self) -> bool:
        """Check if environment is currently publishing."""
        return self.publish_state in self.PUBLISH_STATES_ACTIVE

    @property
    def libraries(self) -> Libraries:
        """
        Get library manager for this environment.

        Returns:
            Libraries: Library management interface
        """
        if self._libraries is None:
            if not self.id:
                raise ValueError("Environment ID not available")
            self._libraries = Libraries(self._client, self._workspace_id, self.id)
        return self._libraries

    def publish(self) -> Dict[str, Any]:  # pragma: no cover
        """
        Publish pending environment changes.

        Initiates publishing of staged library changes. This is a
        long-running operation.

        Returns:
            dict: Publish response with state information

        Raises:
            FabiasError: If publish fails to initiate
        """
        response = self._client.post(f"{self._endpoint}/staging/publish")
        data = response.json()

        self.publish_state = data.get("publishDetails", {}).get("state")
        return data

    def cancelPublish(self) -> Dict[str, Any]:
        """
        Cancel an in-progress publish operation.

        Returns:
            dict: Cancel response with updated state
        """
        response = self._client.post(f"{self._endpoint}/staging/cancelPublish")
        data = response.json()

        self.publish_state = data.get("publishDetails", {}).get("state")
        return data

    def waitForPublish(
        self,
        callback: Optional[Callable[["Environment"], None]] = None,
        timeout: int = 1800,
        poll_interval: int = 30,
    ) -> "Environment":
        """
        Wait for publishing to complete.

        Polls the environment status until publishing completes or fails.

        Args:
            callback: Optional callback(Environment) called after each poll
            timeout: Maximum seconds to wait
            poll_interval: Seconds between status checks

        Returns:
            Environment: Self with final publish state

        Raises:
            FabiasError: If timeout reached or publish fails
        """
        from time import sleep

        max_iterations = timeout // poll_interval
        iteration = 0

        while self.is_publishing:
            if iteration >= max_iterations:
                raise FabiasError(f"Publish timed out after {timeout} seconds")

            sleep(poll_interval)
            self.refresh()

            if callback and callable(callback):
                callback(self)

            iteration += 1

        if self.publish_state == "Failed":
            raise FabiasError("Environment publish failed")

        return self

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


# Set class reference after definition
EnvironmentAccessor.ITEM_CLASS = Environment
