"""
Microsoft Fabric REST API client.

Provides the main FabricClient class for authenticating and interacting with
the Fabric API, with automatic environment detection.
"""

from typing import Optional

from .._shared.auth import AuthProvider, AutoAuth, FabricAuth, ServicePrincipalAuth
from .._shared.client import BaseClient
from .._shared.runtime import runtime
from .workspace import Workspace


class FabricClient(BaseClient):
    """
    HTTP client for Microsoft Fabric REST API.

    Provides authenticated access to Fabric workspaces, pipelines, lakehouses,
    notebooks, and environments. Supports both Fabric-native authentication
    (via notebookutils) and service principal authentication for standalone use.

    Authentication Methods:
        - Inside Fabric: Automatically uses notebookutils for token acquisition
        - Standalone: Uses service principal credentials (passed directly or from env)

    Attributes:
        _base_uri (str): Fabric API v1 endpoint
        _scope (str): OAuth2 scope for Fabric API

    Examples:
        Inside Fabric notebook (automatic auth):

        >>> client = FabricClient()
        >>> workspace = client.workspace()
        >>> pipeline = workspace.pipeline("Daily ETL")
        >>> job = pipeline.run()

        Standalone with explicit credentials:

        >>> client = FabricClient(
        ...     tenant_id="your-tenant",
        ...     client_id="your-client-id",
        ...     client_secret="your-secret"
        ... )
        >>> workspace = client.workspace("GENESIS_EXT")

        Standalone with Key Vault secrets (in Fabric):

        >>> client = FabricClient.fromKeyvault(
        ...     client_id_secret="my-client-id-secret",
        ...     client_secret_secret="my-client-secret-secret"
        ... )
    """

    FABRIC_SCOPE = "https://api.fabric.microsoft.com/.default"
    FABRIC_API_URL = "https://api.fabric.microsoft.com/v1"
    POWERBI_SCOPE = "https://analysis.windows.net/powerbi/api/.default"
    POWERBI_API_URL = "https://api.powerbi.com/v1.0/myorg"

    def __init__(
        self,
        auth: Optional[AuthProvider] = None,
        tenant_id: Optional[str] = None,
        client_id: Optional[str] = None,
        client_secret: Optional[str] = None,
    ):
        """
        Initialize the Fabric client.

        Authentication behavior:
        - If explicit credentials provided: Uses service principal auth (enables admin API)
        - If in Fabric without credentials: Uses sempy.fabric auto-auth (notebookutils)
        - If outside Fabric without credentials: Fails with AuthenticationError

        Args:
            auth: Optional pre-configured AuthProvider
            tenant_id: Azure AD tenant ID (for service principal auth)
            client_id: Application client ID (for service principal auth)
            client_secret: Client secret (for service principal auth)

        Raises:
            AuthenticationError: If credentials required but not provided
        """
        # Track whether explicit credentials were provided
        # This determines which resolution path workspace uses
        self._has_explicit_credentials = auth is not None or all(
            [tenant_id, client_id, client_secret]
        )

        # Determine authentication method
        if auth:
            auth_provider = auth
        elif tenant_id and client_id and client_secret:
            auth_provider = ServicePrincipalAuth(
                tenant_id=tenant_id, client_id=client_id, client_secret=client_secret
            )
        elif runtime.isFabric:  # pragma: no cover
            auth_provider = FabricAuth()
        else:  # pragma: no cover
            # Try AutoAuth which will fail if creds not available
            auth_provider = AutoAuth(
                tenant_id=tenant_id, client_id=client_id, client_secret=client_secret
            )

        super().__init__(auth_provider)
        self._base_uri = self.FABRIC_API_URL
        self._scope = self.FABRIC_SCOPE

        # Store tenant_id and auth provider for lazy extraction
        self._tenant_id = tenant_id  # Store if explicitly provided
        self._tenant_id_extracted = tenant_id is not None  # Track if we've tried extraction
        self._auth_provider = auth_provider  # Keep reference for lazy tenant extraction

    @property
    def hasExplicitCredentials(self) -> bool:
        """Whether explicit credentials (auth or tenant/client/secret) were provided."""
        return self._has_explicit_credentials

    @property
    def tenantId(self) -> Optional[str]:
        """
        Get the tenant ID associated with this client.

        Lazily extracts tenant ID on first access to avoid expensive
        token acquisition during initialization.

        Returns the tenant ID if available from:
        1. Explicitly passed tenant_id parameter
        2. Auth provider's _tenant_id attribute
        3. Extracted from FabricAuth JWT token (lazy loaded)

        Returns:
            Optional[str]: Tenant ID (GUID) or None if not available
        """
        # If we haven't tried extraction yet, do it now
        if not self._tenant_id_extracted:
            self._tenant_id_extracted = True

            # Try to get from auth provider first
            if hasattr(self._auth_provider, "_tenant_id"):
                self._tenant_id = self._auth_provider._tenant_id
            # If using FabricAuth, extract from token
            elif isinstance(self._auth_provider, FabricAuth):
                try:
                    # Get token and decode to extract tenant
                    notebookutils = runtime.notebookutils
                    if notebookutils:
                        token = notebookutils.credentials.getToken("https://graph.microsoft.com")
                        # Decode JWT to get tenant (without verification)
                        import base64
                        import json

                        # JWT format: header.payload.signature
                        parts = token.split(".")
                        if len(parts) >= 2:
                            # Decode payload (add padding if needed)
                            payload = parts[1]
                            payload += "=" * (4 - len(payload) % 4)
                            decoded = base64.b64decode(payload)
                            claims = json.loads(decoded)
                            self._tenant_id = claims.get("tid")  # Tenant ID claim
                except Exception:
                    pass  # Leave as None if extraction fails

        return self._tenant_id

    def workspace(self, workspace_id: Optional[str] = None) -> Workspace:
        """
        Get a workspace object for accessing workspace resources.

        Creates a Workspace instance that provides access to pipelines,
        lakehouses, notebooks, environments, and Git operations.

        Args:
            workspace_id: Workspace identifier. Supports:
                - None: Use current notebook's workspace (Fabric only)
                - "default": Explicitly use current workspace
                - GUID: Lookup by workspace ID
                - Display name: Lookup by workspace name

        Returns:
            Workspace: Workspace object with resolved ID and accessors

        Raises:
            NotFoundError: If workspace cannot be found
            FabiasError: If not in Fabric and no workspace_id provided

        Examples:
            Current workspace (Fabric only):

            >>> workspace = client.workspace()

            By display name:

            >>> workspace = client.workspace("GENESIS_EXT")

            By GUID:

            >>> workspace = client.workspace("6bd19ad7-33b5-4865-b607-7378f6699a66")
        """
        return Workspace(self, workspace_id)

    def workspaces(self) -> list[Workspace]:
        """
        List all accessible workspaces.

        Returns Workspace objects (with lazy loading for properties).
        Uses sempy.fabric when available (in Fabric without credentials),
        otherwise uses REST API.

        Returns:
            list[Workspace]: List of Workspace objects

        Examples:
            >>> workspaces = client.workspaces()
            >>> for ws in workspaces:
            ...     print(f"{ws.name}: {ws.id}")
        """
        # Use sempy if available (in Fabric without credentials)
        if not self.hasExplicitCredentials and runtime.isFabric:
            sempy = runtime.sempy
            if sempy:
                df = sempy.list_workspaces()
                # Convert DataFrame to dict format matching REST API
                workspace_dicts = [
                    {
                        "id": row["Id"],
                        "displayName": row.get("Name"),
                        "capacityId": row.get("Capacity Id"),
                        "description": None,
                    }
                    for _, row in df.iterrows()
                ]
            else:
                # Sempy not available, fall back to REST API (pagination automatic)
                response = self.get("/workspaces")
                workspace_dicts = response.json().get("value", [])
        else:
            # Use REST API (pagination automatic)
            response = self.get("/workspaces")
            workspace_dicts = response.json().get("value", [])

        # Convert dicts to Workspace objects
        return [Workspace(self, workspace_data=w) for w in workspace_dicts]
