"""
Fabric Notebook item.

Provides the Notebook class for accessing Notebook metadata and content.
"""

import base64
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast, overload

from ..._shared.exceptions import FabiasError
from .._shared.enums import NotebookFormat
from ._shared import Item, ItemAccessor

if TYPE_CHECKING:
    from .._client import FabricClient


@dataclass
class NotebookFile:
    """Dataclass representing a notebook file part."""

    def __init__(self, payload: dict):
        self.path: str = payload.get("path", "")
        self.type: str = payload.get("payloadType", "InlineBase64")

        raw: str = payload.get("payload", "") or ""

        # Initialize attributes
        self.base64: str = ""
        self.content: str = ""

        # Decode content automatically
        if self.type == "InlineBase64" and raw:
            self.base64 = raw
            try:
                self.content = base64.b64decode(raw).decode("utf-8")
            except Exception:
                self.content = raw
        else:
            # Payload is already plain text, encode it to base64
            self.content = raw
            self.base64 = base64.b64encode(raw.encode("utf-8")).decode("utf-8")


@dataclass
class NotebookDefinition:
    """Dataclass representing a notebook definition part."""

    def __init__(self, definition: dict):
        payload: dict[str, Any] = definition.get("definition", {})

        if not payload:
            raise ValueError("Invalid notebook definition: 'definition' key not found")

        self.files: List[NotebookFile] = [NotebookFile(part) for part in payload.get("parts", [])]


class NotebookAccessor(ItemAccessor):
    """Accessor for notebook operations."""

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

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

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

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

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


class Notebook(Item):
    """
    Represents a Microsoft Fabric Notebook.

    Provides access to notebook metadata and content.

    Examples:
        >>> notebook = workspace.notebook("ETL Processing")
        >>> print(f"Notebook: {notebook.name} ({notebook.id})")

        >>> # Get notebook definition (long-running operation)
        >>> response = notebook.getDefinition()
        >>> response.wait()
        >>> definition = response.result()
        >>>
        >>> # Decode notebook content from base64
        >>> files = notebook.definition.files
        >>> for file in files:
        ...     print(f"{file.path}: {file.content[:100]}...")
    """

    ITEM_TYPE = "Notebook"
    ITEM_TYPE_DISPLAY = "Notebook"

    def __init__(
        self,
        client: "FabricClient",
        workspace_id: str,
        identifier: Optional[str] = None,
        item_data: Optional[Dict[str, Any]] = None,
    ):
        super().__init__(client, workspace_id, identifier, item_data)
        self._definition_cache: Optional[NotebookDefinition] = (
            None  # Cache for notebook definition data
        )

    @property
    def definition(self) -> Optional[NotebookDefinition]:
        return self.getDefinition()

    def getDefinition(
        self, format: NotebookFormat = NotebookFormat.GITSOURCE, freshLoad: bool = False
    ) -> NotebookDefinition:
        """
        Get notebook definition and content (returns long-running operation).

        This initiates an async operation to retrieve the notebook's definition,
        which includes the notebook content encoded as base64.

        Attributes:
            format (NotebookFormat):  Desired notebook format (currently only GITSOURCE supported)
            freshLoad (bool): If True, bypasses cached definition and fetches anew

        Returns:
            List[Dict[str, Any]]: Files or file sections contained in definition

        Examples:
            >>> response = notebook.getDefinition()
            >>> response.wait()
            >>> definition = response.result()
            >>> print(definition['definition']['parts'])
        """

        if self._definition_cache and not freshLoad:
            return self._definition_cache

        endpoint = f"/workspaces/{self.workspace_id}/notebooks/{self.id}/getDefinition?format={format.value}"
        # POST request returns 202 with Location header for polling
        response = self._client.post(endpoint)

        if not response.longRunning:
            if response.successful:
                # If API returns definition directly (non-LRO), cache and return it
                if not response.data:
                    raise FabiasError(f"No definition data returned for notebook '{self.name}'")
                self._definition_cache = NotebookDefinition(response.data)
                return self._definition_cache

            raise FabiasError(
                f"Failed to get definition for notebook '{self.name}': "
                f"{response.status_code} {response.reason}"
            )

        print(f"Fetching definition for notebook '{self.name}' (this may take a moment)...")
        response.wait()
        definition = response.result()
        if not definition:
            raise FabiasError(f"No definition data returned for notebook '{self.name}'")
        self._definition_cache = NotebookDefinition(definition)

        return self._definition_cache

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


# Set class reference after definition
NotebookAccessor.ITEM_CLASS = Notebook
