"""
Unit tests for fabias.fabric module using mocked HTTP calls.

Tests Fabric client, workspaces, connections, and items patterns.
Integration tests are in test_fabric.py and marked with @pytest.mark.integration.
"""

import pytest
import responses

import fabias
from fabias._shared.auth import ServicePrincipalAuth
from fabias._shared.exceptions import FabiasError, NotFoundError

# =============================================================================
# FIXTURES
# =============================================================================


@pytest.fixture
def mock_auth():
    """Mock ServicePrincipalAuth for unit tests"""
    return ServicePrincipalAuth(
        tenant_id="fake-tenant-id", client_id="fake-client-id", client_secret="fake-client-secret"
    )


@pytest.fixture
def sample_workspace_data():
    """Sample workspace data matching Fabric API response"""
    return {
        "id": "workspace-guid-123",
        "displayName": "TestWorkspace",
        "capacityId": "capacity-guid-456",
        "description": "A test workspace",
    }


@pytest.fixture
def sample_workspaces_list():
    """Sample list of workspaces"""
    return {
        "value": [
            {
                "id": "ws-guid-1",
                "displayName": "Workspace1",
                "capacityId": "cap-1",
                "description": "First workspace",
            },
            {
                "id": "ws-guid-2",
                "displayName": "Workspace2",
                "capacityId": "cap-2",
                "description": "Second workspace",
            },
        ]
    }


@pytest.fixture
def sample_connection_data():
    """Sample connection data"""
    return {
        "id": "conn-guid-123",
        "displayName": "TestConnection",
        "connectivityType": "Cloud",
        "privacyLevel": "Organizational",
        "credentialDetails": {"connectionEncryption": "Encrypted"},
    }


@pytest.fixture
def sample_connections_list():
    """Sample list of connections"""
    return {
        "value": [
            {
                "id": "conn-1",
                "displayName": "SQL Server",
                "connectivityType": "OnPremises",
                "privacyLevel": "Organizational",
            },
            {
                "id": "conn-2",
                "displayName": "Azure Blob",
                "connectivityType": "Cloud",
                "privacyLevel": "Public",
            },
        ]
    }


@pytest.fixture
def sample_items_list():
    """Sample workspace items list"""
    return {
        "value": [
            {
                "id": "item-1",
                "displayName": "ETL Pipeline",
                "type": "DataPipeline",
                "workspaceId": "workspace-guid-123",
            },
            {
                "id": "item-2",
                "displayName": "Analytics",
                "type": "Lakehouse",
                "workspaceId": "workspace-guid-123",
            },
            {
                "id": "item-3",
                "displayName": "Report Notebook",
                "type": "Notebook",
                "workspaceId": "workspace-guid-123",
            },
        ]
    }


@pytest.fixture(autouse=True)
def reset_fabric_module():
    """Reset fabric module state before each test"""
    fabias.reset()
    yield
    fabias.reset()


# =============================================================================
# CLIENT TESTS
# =============================================================================


class TestFabricClient:
    """Test FabricClient initialization and configuration"""

    def test_client_with_explicit_auth(self, mock_auth):
        """Test client creation with explicit auth provider"""
        client = fabias.client(auth=mock_auth)

        assert client is not None
        assert client.hasExplicitCredentials is True
        assert client._base_uri == "https://api.fabric.microsoft.com/v1"

    def test_client_with_credentials(self):
        """Test client creation with individual credentials"""
        client = fabias.client(
            tenant_id="test-tenant", client_id="test-client", client_secret="test-secret"
        )

        assert client is not None
        assert client.hasExplicitCredentials is True
        assert client.tenantId == "test-tenant"

    def test_client_sets_module_singleton(self, mock_auth):
        """Test that client() sets the module-level singleton"""
        fabias.reset()

        client = fabias.client(auth=mock_auth)

        # Module should now use this client
        assert fabias._fabric._client is client
        assert fabias._fabric._configured is True

    def test_reset_clears_singleton(self, mock_auth):
        """Test that reset() clears the module state"""
        fabias.client(auth=mock_auth)
        assert fabias._fabric._client is not None

        fabias.reset()

        assert fabias._fabric._client is None
        assert fabias._fabric._configured is False


# =============================================================================
# WORKSPACE TESTS
# =============================================================================


class TestWorkspace:
    """Test Workspace class functionality"""

    @responses.activate
    def test_workspace_by_guid_no_api_call(self, mock_auth):
        """Test workspace lookup by GUID doesn't make API call"""
        fabias.client(auth=mock_auth)

        # No responses mocked - if it tries to call API, test will fail
        ws = fabias.workspace("6bd19ad7-33b5-4865-b607-7378f6699a66")

        # ID should be set immediately
        assert ws.id == "6bd19ad7-33b5-4865-b607-7378f6699a66"
        # No API calls should have been made
        assert len(responses.calls) == 0

    @responses.activate
    def test_workspace_by_name_resolves(self, mock_auth, sample_workspace_data):
        """Test workspace lookup by name makes API call"""
        # Mock auth token
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        # Mock admin workspaces endpoint (fails with 403 to trigger fallback)
        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/admin/workspaces",
            json={"error": "Forbidden"},
            status=403,
        )

        # Mock fallback to user workspaces list
        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces",
            json={"value": [sample_workspace_data]},
            status=200,
        )

        fabias.client(auth=mock_auth)
        ws = fabias.workspace("TestWorkspace")

        assert ws.id == "workspace-guid-123"
        assert ws.name == "TestWorkspace"

    @responses.activate
    def test_workspace_not_found_raises_error(self, mock_auth):
        """Test workspace lookup raises NotFoundError when not found"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        # Mock empty response from admin API
        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/admin/workspaces?name=NonExistent",
            json={"workspaces": []},
            status=200,
        )

        # Mock empty response from user API fallback
        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces",
            json={"value": []},
            status=200,
        )

        fabias.client(auth=mock_auth)

        with pytest.raises(NotFoundError):
            fabias.workspace("NonExistent")

    @responses.activate
    def test_workspace_from_data_no_api_call(self, mock_auth, sample_workspace_data):
        """Test workspace created from data doesn't make API calls"""
        fabias.client(auth=mock_auth)

        from fabias._fabric.workspace import Workspace

        ws = Workspace(fabias._fabric._client, workspace_data=sample_workspace_data)

        assert ws.id == "workspace-guid-123"
        assert ws.name == "TestWorkspace"
        assert ws.capacityId == "capacity-guid-456"
        assert ws.description == "A test workspace"
        assert len(responses.calls) == 0


class TestWorkspaceItems:
    """Test workspace item accessors"""

    @responses.activate
    def test_workspace_items_returns_typed_objects(self, mock_auth, sample_items_list):
        """Test items() returns properly typed objects"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        # Mock items endpoint
        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/ws-guid-123/items",
            json=sample_items_list,
            status=200,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.workspace import Workspace

        # Use workspace_data to skip resolution
        ws = Workspace(
            fabias._fabric._client,
            workspace_data={"id": "ws-guid-123", "displayName": "TestWorkspace"},
        )

        items = ws.items()

        assert len(items) == 3

        # Check types are correct
        from fabias._fabric.items import Lakehouse, Notebook, Pipeline

        pipeline = next((i for i in items if i.name == "ETL Pipeline"), None)
        lakehouse = next((i for i in items if i.name == "Analytics"), None)
        notebook = next((i for i in items if i.name == "Report Notebook"), None)

        assert isinstance(pipeline, Pipeline)
        assert isinstance(lakehouse, Lakehouse)
        assert isinstance(notebook, Notebook)

    @responses.activate
    def test_workspace_pipelines_filters_correctly(self, mock_auth):
        """Test pipelines() returns only pipelines"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        # pipelines() calls items?type=DataPipeline - return only pipelines
        pipelines_only = {
            "value": [
                {"id": "item-1", "displayName": "ETL Pipeline", "type": "DataPipeline"},
            ]
        }
        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/ws-guid-123/items?type=DataPipeline",
            json=pipelines_only,
            status=200,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.workspace import Workspace

        # Use workspace_data to skip resolution
        ws = Workspace(
            fabias._fabric._client,
            workspace_data={"id": "ws-guid-123", "displayName": "TestWorkspace"},
        )

        pipelines = ws.pipelines()

        assert len(pipelines) == 1
        assert pipelines[0].name == "ETL Pipeline"


# =============================================================================
# CONNECTION TESTS
# =============================================================================


class TestConnections:
    """Test Connection class and accessor"""

    def test_connection_from_guid_via_data(self, mock_auth, sample_connection_data):
        """Test connection pre-populated from data doesn't make API call"""
        fabias.client(auth=mock_auth)

        from fabias._fabric.connections import Connection

        # Use connection_data to skip resolution
        conn = Connection(fabias._fabric._client, connection_data=sample_connection_data)

        # All data available immediately
        assert conn.id == "conn-guid-123"
        assert conn.name == "TestConnection"
        assert conn._data_fetched is True

    @responses.activate
    def test_connection_from_data_no_api_call(self, mock_auth, sample_connection_data):
        """Test connection from data doesn't make API calls"""
        fabias.client(auth=mock_auth)

        from fabias._fabric.connections import Connection

        conn = Connection(fabias._fabric._client, connection_data=sample_connection_data)

        assert conn.id == "conn-guid-123"
        assert conn.name == "TestConnection"
        assert conn.connectivityType == "Cloud"
        assert len(responses.calls) == 0

    @responses.activate
    def test_connections_list_all(self, mock_auth, sample_connections_list):
        """Test listing all connections"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/connections",
            json=sample_connections_list,
            status=200,
        )

        fabias.client(auth=mock_auth)

        conns = fabias.connections()

        assert len(conns) == 2
        assert conns[0].name == "SQL Server"
        assert conns[1].name == "Azure Blob"

    @responses.activate
    def test_connection_not_found(self, mock_auth):
        """Test connection lookup raises NotFoundError"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        # Mock connections list (fabias.connection searches through this)
        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/connections",
            json={"value": []},
            status=200,
            match_querystring=False,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.connections import Connections

        handler = Connections(fabias._fabric._client)

        # Using handler.list with name should return empty
        matches = handler.list(name="NonExistent")
        assert len(matches) == 0


# =============================================================================
# WORKSPACES ACCESSOR TESTS
# =============================================================================


class TestWorkspacesAccessor:
    """Test WorkspaceAccessor (fabias.workspaces())"""

    @responses.activate
    def test_list_workspaces(self, mock_auth, sample_workspaces_list):
        """Test listing workspaces returns Workspace objects"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces",
            json=sample_workspaces_list,
            status=200,
        )

        fabias.client(auth=mock_auth)

        workspaces = fabias.workspaces()

        assert len(workspaces) == 2
        assert workspaces[0].name == "Workspace1"
        assert workspaces[1].id == "ws-guid-2"

    @responses.activate
    def test_add_workspace(self, mock_auth):
        """Test creating a new workspace"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        # Mock the create workspace POST
        responses.add(
            responses.POST,
            "https://api.fabric.microsoft.com/v1/workspaces",
            json={
                "id": "new-ws-guid",
                "displayName": "NewWorkspace",
                "capacityId": "cap-123",
                "description": "New test workspace",
            },
            status=201,
        )

        # Mock workspaces list (called during resolution)
        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces",
            json={
                "value": [
                    {
                        "id": "new-ws-guid",
                        "displayName": "NewWorkspace",
                        "capacityId": "cap-123",
                        "description": "New test workspace",
                    }
                ]
            },
            status=200,
            match_querystring=False,
        )

        fabias.client(auth=mock_auth)

        new_ws = fabias.workspaces.add(
            name="NewWorkspace", capacity="cap-123", description="New test workspace"
        )

        assert new_ws.id == "new-ws-guid"
        assert new_ws.name == "NewWorkspace"


# =============================================================================
# ROLE ASSIGNMENT TESTS
# =============================================================================


class TestRoleAssignments:
    """Test role assignment patterns"""

    @responses.activate
    def test_connection_role_assignments_list(self, mock_auth, sample_connection_data):
        """Test listing connection role assignments"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/connections/conn-123/roleAssignments",
            json={
                "value": [
                    {
                        "id": "assign-1",
                        "principal": {"id": "user-1", "type": "User"},
                        "role": "Owner",
                    },
                    {
                        "id": "assign-2",
                        "principal": {"id": "group-1", "type": "Group"},
                        "role": "User",
                    },
                ]
            },
            status=200,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.connections import Connection

        conn = Connection(
            fabias._fabric._client,
            connection_data={"id": "conn-123", "displayName": "Test", "connectivityType": "Cloud"},
        )

        assignments = conn.roleAssignments()

        assert len(assignments) == 2
        assert assignments[0].role == "Owner"
        assert assignments[1].principalType == "Group"


# =============================================================================
# ONELAKE SECURITY TESTS
# =============================================================================


class TestOneLakeSecurity:
    """Test OneLake data access security (ABAC) patterns"""

    def test_rule_path_normalization_in_tojson(self):
        """Test Rule path normalization happens in toJson()"""
        from fabias import ReadWrite
        from fabias._fabric.items.permissions import Rule

        # Create rule with user-friendly shortcuts
        rule = Rule(paths=["tables"], accessLevel=ReadWrite.READ)

        # paths stores original input
        assert rule.paths == ["tables"]

        # Normalization happens in toJson
        json_output = rule.toJson()
        # The path should be normalized to /Tables/ in the output
        assert json_output["permission"][0]["attributeValueIncludedIn"] == ["/Tables/"]

    def test_rule_normalizer_static_methods(self):
        """Test _TableNormalizer static methods directly"""
        from fabias._fabric.items.permissions.rules import _TableNormalizer

        # Test normalizePathPattern
        assert _TableNormalizer.normalizePathPattern("*") == "*"
        assert _TableNormalizer.normalizePathPattern("tables") == "/Tables/"
        assert _TableNormalizer.normalizePathPattern("Tables") == "/Tables/"
        assert _TableNormalizer.normalizePathPattern("files") == "/Files/"
        assert _TableNormalizer.normalizePathPattern("dbo.*") == "/Tables/dbo"
        assert _TableNormalizer.normalizePathPattern("/Tables/custom") == "/Tables/custom"

    def test_rule_explicit_paths_in_tojson(self):
        """Test explicit paths pass through in toJson"""
        from fabias import ReadWrite
        from fabias._fabric.items.permissions import Rule

        rule = Rule(paths=["/Tables/dbo/Customers"], accessLevel=ReadWrite.READ)
        json_output = rule.toJson()

        assert json_output["permission"][0]["attributeValueIncludedIn"] == ["/Tables/dbo/Customers"]

    def test_role_dry_vs_live(self):
        """Test Role dry (template) vs live (API-connected) state"""
        from fabias import ReadWrite
        from fabias._fabric.items.permissions import Role, Rule

        # Dry role (no client context)
        dry_role = Role(name="Reader", rules=[Rule(paths=["*"], accessLevel=ReadWrite.READ)])

        # Should not have client context
        assert dry_role._client is None
        assert dry_role._workspace_id is None

    def test_fabric_item_member(self, mock_auth):
        """Test FabricItem member creation"""
        # Need to configure client first for FabricItem
        fabias.client(auth=mock_auth)

        from fabias import ItemAccess
        from fabias._fabric.items.permissions import FabricItem

        member = FabricItem(access=[ItemAccess.READ])

        assert member.access == [ItemAccess.READ]
        assert member.path is None  # Will be hydrated later

    def test_entra_member_creation(self, mock_auth):
        """Test EntraMember creation variations"""
        # Need to configure client first
        fabias.client(auth=mock_auth)

        from fabias._fabric.items.permissions import EntraMember

        # With object ID only
        member1 = EntraMember(object="user-guid")
        assert member1.object == "user-guid"

        # With both tenant and object
        member2 = EntraMember(tenant="tenant-guid", object="user-guid")
        assert member2.tenant == "tenant-guid"
        assert member2.object == "user-guid"


# =============================================================================
# VARIABLE LIBRARY TESTS
# =============================================================================


class TestVariableLibrary:
    """Tests for VariableLibrary item and VariableLibraryAccessor."""

    WS_ID = "ws-guid-vl"
    VL_ID = "vl-guid-001"
    BASE_URL = "https://api.fabric.microsoft.com/v1"
    TOKEN_URL = "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token"

    def _token_mock(self):
        responses.add(
            responses.POST,
            self.TOKEN_URL,
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

    def _ws_data(self, ws_id: str | None = None) -> dict:
        return {"id": ws_id or self.WS_ID, "displayName": "TestWorkspace"}

    def _vl_data(
        self, *, vl_id: str | None = None, name: str = "Deployment Config", active: str = "Default"
    ) -> dict:
        return {
            "id": vl_id or self.VL_ID,
            "displayName": name,
            "description": "Stage-specific variables",
            "type": "VariableLibrary",
            "workspaceId": self.WS_ID,
            "properties": {"activeValueSetName": active},
        }

    # -------------------------------------------------------------------------
    # Initialisation / fromJson
    # -------------------------------------------------------------------------

    def test_variable_library_from_item_data(self, mock_auth):
        """Pre-populated item_data skips API call and parses properties."""
        fabias.client(auth=mock_auth)

        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(
            fabias._fabric._client,
            self.WS_ID,
            item_data=self._vl_data(),
        )

        assert vl.id == self.VL_ID
        assert vl.name == "Deployment Config"
        assert vl.active_value_set == "Default"

    def test_variable_library_repr(self, mock_auth):
        """__repr__ includes id, name, and active_value_set."""
        fabias.client(auth=mock_auth)

        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=self._vl_data())
        r = repr(vl)

        assert "VariableLibrary" in r
        assert "Deployment Config" in r
        assert "Default" in r

    def test_variable_library_active_value_set_none_when_missing(self, mock_auth):
        """active_value_set is None when properties are absent."""
        fabias.client(auth=mock_auth)

        from fabias._fabric.items.variablelibrary import VariableLibrary

        data = {
            "id": self.VL_ID,
            "displayName": "NoProps",
            "type": "VariableLibrary",
            "workspaceId": self.WS_ID,
        }
        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=data)

        assert vl.active_value_set is None

    # -------------------------------------------------------------------------
    # factory / _TYPE_MAP
    # -------------------------------------------------------------------------

    @responses.activate
    def test_workspace_items_returns_variable_library(self, mock_auth):
        """fromJson factory maps VariableLibrary type to VariableLibrary class."""
        self._token_mock()

        responses.add(
            responses.GET,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/items",
            json={"value": [self._vl_data()]},
            status=200,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.workspace import Workspace

        ws = Workspace(fabias._fabric._client, workspace_data=self._ws_data())
        items = ws.items()

        from fabias._fabric.items import VariableLibrary

        vl = next((i for i in items if i.name == "Deployment Config"), None)
        assert vl is not None
        assert isinstance(vl, VariableLibrary)
        assert vl.active_value_set == "Default"

    # -------------------------------------------------------------------------
    # Accessor – list
    # -------------------------------------------------------------------------

    @responses.activate
    def test_variable_libraries_list(self, mock_auth):
        """variableLibraries() lists all variable libraries in the workspace."""
        self._token_mock()

        responses.add(
            responses.GET,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/items?type=VariableLibrary",
            json={"value": [self._vl_data(), self._vl_data(vl_id="vl-guid-002", name="Other Lib")]},
            status=200,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.workspace import Workspace

        ws = Workspace(fabias._fabric._client, workspace_data=self._ws_data())
        libs = ws.variableLibraries()

        assert len(libs) == 2
        assert libs[0].name == "Deployment Config"
        assert libs[1].name == "Other Lib"

    # -------------------------------------------------------------------------
    # Accessor – add (create)
    # -------------------------------------------------------------------------

    @responses.activate
    def test_variable_libraries_add(self, mock_auth):
        """variableLibraries.add() POSTs to VariableLibraries endpoint."""
        self._token_mock()

        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries",
            json=self._vl_data(),
            status=201,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.workspace import Workspace

        ws = Workspace(fabias._fabric._client, workspace_data=self._ws_data())
        lib = ws.variableLibraries.add("Deployment Config")

        from fabias._fabric.items import VariableLibrary

        assert isinstance(lib, VariableLibrary)
        assert lib.id == self.VL_ID
        assert lib.name == "Deployment Config"

        # Verify correct endpoint was called
        assert "/VariableLibraries" in responses.calls[-1].request.url

    @responses.activate
    def test_variable_libraries_add_with_description(self, mock_auth):
        """variableLibraries.add() includes description in request payload."""
        self._token_mock()

        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries",
            json=self._vl_data(),
            status=201,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.workspace import Workspace

        ws = Workspace(fabias._fabric._client, workspace_data=self._ws_data())
        ws.variableLibraries.add("Deployment Config", description="Stage variables")

        import json as _json

        body = _json.loads(responses.calls[-1].request.body)
        assert body["description"] == "Stage variables"

    @responses.activate
    def test_variable_libraries_add_409_returns_existing(self, mock_auth):
        """variableLibraries.add() returns existing library on 409 conflict."""
        self._token_mock()

        # First call: 409 Conflict
        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries",
            json={"errorCode": "ItemDisplayNameAlreadyInUse", "message": "Already exists"},
            status=409,
        )

        # Second call: list items (accessor fallback)
        responses.add(
            responses.GET,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/items?type=VariableLibrary",
            json={"value": [self._vl_data()]},
            status=200,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.workspace import Workspace

        ws = Workspace(fabias._fabric._client, workspace_data=self._ws_data())
        lib = ws.variableLibraries.add("Deployment Config")

        assert lib.name == "Deployment Config"

    # -------------------------------------------------------------------------
    # workspace.variableLibrary() – singular lookup
    # -------------------------------------------------------------------------

    @responses.activate
    def test_workspace_variable_library_by_name(self, mock_auth):
        """workspace.variableLibrary() resolves by name and fetches properties."""
        self._token_mock()
        self._token_mock()  # second token call for refresh()

        # List endpoint for name resolution
        responses.add(
            responses.GET,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/items?type=VariableLibrary",
            json={"value": [self._vl_data()]},
            status=200,
        )

        # Full details endpoint called by refresh()
        responses.add(
            responses.GET,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}",
            json=self._vl_data(active="Production"),
            status=200,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.workspace import Workspace

        ws = Workspace(fabias._fabric._client, workspace_data=self._ws_data())
        lib = ws.variableLibrary("Deployment Config")

        assert lib.id == self.VL_ID
        assert lib.active_value_set == "Production"

    @responses.activate
    def test_workspace_variable_library_not_found(self, mock_auth):
        """workspace.variableLibrary() raises NotFoundError for unknown name."""
        self._token_mock()

        responses.add(
            responses.GET,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/items?type=VariableLibrary",
            json={"value": []},
            status=200,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.workspace import Workspace
        from fabias._shared.exceptions import NotFoundError

        ws = Workspace(fabias._fabric._client, workspace_data=self._ws_data())

        with pytest.raises(NotFoundError):
            ws.variableLibrary("Nonexistent")

    # -------------------------------------------------------------------------
    # refresh()
    # -------------------------------------------------------------------------

    @responses.activate
    def test_refresh(self, mock_auth):
        """refresh() re-fetches metadata including active_value_set."""
        self._token_mock()

        responses.add(
            responses.GET,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}",
            json=self._vl_data(active="Staging"),
            status=200,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(
            fabias._fabric._client, self.WS_ID, item_data=self._vl_data(active="Default")
        )
        assert vl.active_value_set == "Default"

        result = vl.refresh()

        assert result is vl  # method chaining
        assert vl.active_value_set == "Staging"

    # -------------------------------------------------------------------------
    # activateValueSet()
    # -------------------------------------------------------------------------

    @responses.activate
    def test_activate_value_set(self, mock_auth):
        """activateValueSet() PATCHes activeValueSetName and updates local state."""
        self._token_mock()

        responses.add(
            responses.PATCH,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}",
            json=self._vl_data(active="Production"),
            status=200,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(
            fabias._fabric._client, self.WS_ID, item_data=self._vl_data(active="Default")
        )
        result = vl.activateValueSet("Production")

        assert result is vl  # method chaining
        assert vl.active_value_set == "Production"

        import json as _json

        body = _json.loads(responses.calls[-1].request.body)
        assert body["properties"]["activeValueSetName"] == "Production"

    # -------------------------------------------------------------------------
    # getDefinition()
    # -------------------------------------------------------------------------

    @responses.activate
    def test_get_definition(self, mock_auth):
        """getDefinition() POSTs to getDefinition endpoint and returns parts."""
        self._token_mock()

        definition_response = {
            "definition": {
                "format": "VariableLibraryV1",
                "parts": [
                    {
                        "path": "variables.json",
                        "payload": "eyJ2YXJzIjpbXX0=",
                        "payloadType": "InlineBase64",
                    },
                    {
                        "path": "settings.json",
                        "payload": "eyJvcmRlciI6W119",
                        "payloadType": "InlineBase64",
                    },
                ],
            }
        }

        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/getDefinition",
            json=definition_response,
            status=200,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=self._vl_data())
        definition = vl.getDefinition()

        assert definition["format"] == "VariableLibraryV1"
        assert len(definition["parts"]) == 2
        paths = [p["path"] for p in definition["parts"]]
        assert "variables.json" in paths

    # -------------------------------------------------------------------------
    # updateDefinition()
    # -------------------------------------------------------------------------

    @responses.activate
    def test_update_definition(self, mock_auth):
        """updateDefinition() POSTs to updateDefinition endpoint then refreshes."""
        self._token_mock()
        self._token_mock()  # second token for refresh()

        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/updateDefinition",
            json={},
            status=200,
        )

        # refresh() call after update
        responses.add(
            responses.GET,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}",
            json=self._vl_data(),
            status=200,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=self._vl_data())
        parts = [
            {"path": "variables.json", "payload": "eyJ2YXJzIjpbXX0=", "payloadType": "InlineBase64"}
        ]
        result = vl.updateDefinition(parts)

        assert result is vl  # method chaining

        import json as _json

        update_call = responses.calls[-2]  # before refresh GET
        body = _json.loads(update_call.request.body)
        assert body["definition"]["format"] == "VariableLibraryV1"
        assert body["definition"]["parts"][0]["path"] == "variables.json"

    # -------------------------------------------------------------------------
    # update()
    # -------------------------------------------------------------------------

    @responses.activate
    def test_update_metadata(self, mock_auth):
        """update() PATCHes display name / description."""
        self._token_mock()

        updated = self._vl_data(name="New Name")
        updated["description"] = "New desc"

        responses.add(
            responses.PATCH,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}",
            json=updated,
            status=200,
        )

        fabias.client(auth=mock_auth)

        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=self._vl_data())
        result = vl.update(name="New Name", description="New desc")

        assert result is vl
        assert vl.name == "New Name"
        assert vl.description == "New desc"

    def test_update_noop_when_nothing_provided(self, mock_auth):
        """update() with no args is a no-op (no API call)."""
        fabias.client(auth=mock_auth)

        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=self._vl_data())
        # Should not raise or call API
        result = vl.update()

        assert result is vl

    # -------------------------------------------------------------------------
    # variables() / setVariables()
    # -------------------------------------------------------------------------

    def _definition_data(self, extra_parts: list | None = None) -> dict:
        """Return a realistic definition response with real base64 payloads."""
        import base64
        import json as _json

        variables_content = {
            "$schema": "https://developer.microsoft.com/json-schemas/fabric/item/variableLibrary/definition/variables/1.0.0/schema.json",
            "variables": [
                {"name": "environment", "type": "String", "value": "dev"},
                {"name": "maxRetries", "type": "Integer", "value": 3},
            ],
        }
        settings_content = {
            "$schema": "https://developer.microsoft.com/json-schemas/fabric/item/variableLibrary/definition/settings/1.0.0/schema.json",
            "valueSetsOrder": ["Production"],
        }
        prod_valueset = {
            "$schema": "https://developer.microsoft.com/json-schemas/fabric/item/variableLibrary/definition/valueSet/1.0.0/schema.json",
            "name": "Production",
            "variableOverrides": [{"name": "environment", "value": "prod"}],
        }

        def enc(d):
            return base64.b64encode(_json.dumps(d).encode()).decode()

        parts = [
            {
                "path": "variables.json",
                "payload": enc(variables_content),
                "payloadType": "InlineBase64",
            },
            {
                "path": "valueSets/Production.json",
                "payload": enc(prod_valueset),
                "payloadType": "InlineBase64",
            },
            {
                "path": "settings.json",
                "payload": enc(settings_content),
                "payloadType": "InlineBase64",
            },
        ]
        if extra_parts:
            parts.extend(extra_parts)
        return {"definition": {"format": "VariableLibraryV1", "parts": parts}}

    @responses.activate
    def test_variables_returns_decoded_list(self, mock_auth):
        """variables() fetches definition and returns decoded variable list."""
        self._token_mock()

        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/getDefinition",
            json=self._definition_data(),
            status=200,
        )

        fabias.client(auth=mock_auth)
        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=self._vl_data())
        result = vl.variables

        assert len(result) == 2
        assert result["environment"].name == "environment"
        assert result["environment"].value == "dev"
        assert result["maxRetries"].name == "maxRetries"
        assert result["maxRetries"].value == 3

    @responses.activate
    def test_variables_returns_empty_when_no_variables_part(self, mock_auth):
        """variables() returns [] when variables.json is absent."""
        self._token_mock()

        import base64
        import json as _json

        settings_part = {
            "path": "settings.json",
            "payload": base64.b64encode(_json.dumps({"valueSetsOrder": []}).encode()).decode(),
            "payloadType": "InlineBase64",
        }
        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/getDefinition",
            json={"definition": {"format": "VariableLibraryV1", "parts": [settings_part]}},
            status=200,
        )

        fabias.client(auth=mock_auth)
        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=self._vl_data())
        assert vl.variables() == []

    @responses.activate
    def test_set_variables_replaces_variables_part(self, mock_auth):
        """setVariables() fetches definition, replaces variables.json, pushes back."""
        self._token_mock()
        self._token_mock()  # refresh after updateDefinition

        # getDefinition call
        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/getDefinition",
            json=self._definition_data(),
            status=200,
        )

        # updateDefinition call
        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/updateDefinition",
            json={},
            status=200,
        )

        # refresh() call
        responses.add(
            responses.GET,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}",
            json=self._vl_data(),
            status=200,
        )

        fabias.client(auth=mock_auth)
        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=self._vl_data())
        result = vl.setVariables(
            [
                {"name": "environment", "type": "String", "value": "prod"},
                {"name": "maxRetries", "type": "Integer", "value": 10},
            ]
        )

        assert result is vl

        # Check the updateDefinition payload
        import base64
        import json as _json

        update_call = next(c for c in responses.calls if "updateDefinition" in c.request.url)
        body = _json.loads(update_call.request.body)
        parts = body["definition"]["parts"]

        # variables.json should be present and updated
        vars_part = next(p for p in parts if p["path"] == "variables.json")
        decoded = _json.loads(base64.b64decode(vars_part["payload"]))
        assert decoded["variables"][0]["value"] == "prod"
        assert decoded["variables"][1]["value"] == 10

        # Existing value set and settings parts should be preserved
        paths = [p["path"] for p in parts]
        assert "valueSets/Production.json" in paths
        assert "settings.json" in paths

    @responses.activate
    def test_set_variables_preserves_existing_parts(self, mock_auth):
        """setVariables() keeps existing valueSets and settings parts untouched."""
        self._token_mock()
        self._token_mock()

        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/getDefinition",
            json=self._definition_data(),
            status=200,
        )
        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/updateDefinition",
            json={},
            status=200,
        )
        responses.add(
            responses.GET,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}",
            json=self._vl_data(),
            status=200,
        )

        fabias.client(auth=mock_auth)
        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=self._vl_data())
        vl.setVariables([{"name": "x", "type": "String", "value": "y"}])

        import json as _json

        body = _json.loads(
            next(c for c in responses.calls if "updateDefinition" in c.request.url).request.body
        )
        paths = [p["path"] for p in body["definition"]["parts"]]
        assert "valueSets/Production.json" in paths
        assert "settings.json" in paths

    # -------------------------------------------------------------------------
    # valueSets() / setValueSet() / deleteValueSet()
    # -------------------------------------------------------------------------

    @responses.activate
    def test_value_sets_returns_decoded_list(self, mock_auth):
        """valueSets() returns decoded value set dicts from definition parts."""
        self._token_mock()

        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/getDefinition",
            json=self._definition_data(),
            status=200,
        )

        fabias.client(auth=mock_auth)
        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=self._vl_data())
        result = vl.valueSets()

        assert len(result) == 1
        assert result[0]["name"] == "Production"
        assert result[0]["variableOverrides"][0] == {"name": "environment", "value": "prod"}

    @responses.activate
    def test_set_value_set_adds_new_part(self, mock_auth):
        """setValueSet() adds a new valueSets part to the definition."""
        self._token_mock()
        self._token_mock()

        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/getDefinition",
            json=self._definition_data(),
            status=200,
        )
        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/updateDefinition",
            json={},
            status=200,
        )
        responses.add(
            responses.GET,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}",
            json=self._vl_data(),
            status=200,
        )

        fabias.client(auth=mock_auth)
        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=self._vl_data())
        result = vl.setValueSet("Staging", {"environment": "staging", "maxRetries": "2"})

        assert result is vl

        import base64
        import json as _json

        body = _json.loads(
            next(c for c in responses.calls if "updateDefinition" in c.request.url).request.body
        )
        parts = body["definition"]["parts"]
        staging_part = next((p for p in parts if p["path"] == "valueSets/Staging.json"), None)
        assert staging_part is not None

        decoded = _json.loads(base64.b64decode(staging_part["payload"]))
        assert decoded["name"] == "Staging"
        assert {"name": "environment", "value": "staging"} in decoded["variableOverrides"]

        # Existing Production value set preserved
        assert any(p["path"] == "valueSets/Production.json" for p in parts)

    @responses.activate
    def test_set_value_set_replaces_existing(self, mock_auth):
        """setValueSet() replaces an existing value set by the same name."""
        self._token_mock()
        self._token_mock()

        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/getDefinition",
            json=self._definition_data(),
            status=200,
        )
        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/updateDefinition",
            json={},
            status=200,
        )
        responses.add(
            responses.GET,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}",
            json=self._vl_data(),
            status=200,
        )

        fabias.client(auth=mock_auth)
        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=self._vl_data())
        vl.setValueSet("Production", {"environment": "prod-v2"})

        import json as _json

        body = _json.loads(
            next(c for c in responses.calls if "updateDefinition" in c.request.url).request.body
        )
        prod_parts = [
            p for p in body["definition"]["parts"] if p["path"] == "valueSets/Production.json"
        ]
        assert len(prod_parts) == 1  # not duplicated

    @responses.activate
    def test_delete_value_set_removes_part(self, mock_auth):
        """deleteValueSet() removes the named value set part from the definition."""
        self._token_mock()
        self._token_mock()

        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/getDefinition",
            json=self._definition_data(),
            status=200,
        )
        responses.add(
            responses.POST,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}/updateDefinition",
            json={},
            status=200,
        )
        responses.add(
            responses.GET,
            f"{self.BASE_URL}/workspaces/{self.WS_ID}/VariableLibraries/{self.VL_ID}",
            json=self._vl_data(),
            status=200,
        )

        fabias.client(auth=mock_auth)
        from fabias._fabric.items.variablelibrary import VariableLibrary

        vl = VariableLibrary(fabias._fabric._client, self.WS_ID, item_data=self._vl_data())
        result = vl.deleteValueSet("Production")

        assert result is vl

        import json as _json

        body = _json.loads(
            next(c for c in responses.calls if "updateDefinition" in c.request.url).request.body
        )
        parts = body["definition"]["parts"]
        assert not any(p["path"] == "valueSets/Production.json" for p in parts)
        assert any(p["path"] == "settings.json" for p in parts)  # other parts preserved

    # -------------------------------------------------------------------------
    # Module-level export
    # -------------------------------------------------------------------------

    def test_variable_library_importable_from_fabias(self):
        """VariableLibrary is importable directly from fabias."""
        import fabias

        VL = fabias.VariableLibrary
        assert VL is not None

        from fabias._fabric.items.variablelibrary import VariableLibrary

        assert VL is VariableLibrary


# =============================================================================
# ENUM TESTS
# =============================================================================


class TestEnums:
    """Test enum values are accessible"""

    def test_workspace_role_values(self):
        """Test WorkspaceRole enum values"""
        from fabias import WorkspaceRole

        assert WorkspaceRole.ADMIN.value == "Admin"
        assert WorkspaceRole.MEMBER.value == "Member"
        assert WorkspaceRole.CONTRIBUTOR.value == "Contributor"
        assert WorkspaceRole.VIEWER.value == "Viewer"

    def test_connectivity_type_values(self):
        """Test ConnectivityType enum values"""
        from fabias import ConnectivityType

        assert ConnectivityType.ON_PREMISES_GATEWAY.value == "OnPremisesGateway"
        assert ConnectivityType.SHAREABLE_CLOUD.value == "ShareableCloud"

    def test_item_type_values(self):
        """Test ItemType enum values"""
        from fabias import ItemType

        assert ItemType.DATA_PIPELINE.value == "DataPipeline"
        assert ItemType.LAKEHOUSE.value == "Lakehouse"
        assert ItemType.NOTEBOOK.value == "Notebook"

    def test_read_write_values(self):
        """Test ReadWrite enum for permissions"""
        from fabias import ReadWrite

        assert ReadWrite.READ.value == "Read"
        assert ReadWrite.READWRITE.value == "ReadWrite"


# =============================================================================
# FOLDER TESTS
# =============================================================================


class TestFolders:
    """Test Folder and FolderAccessor patterns"""

    @pytest.fixture
    def sample_folder_data(self):
        """Sample folder data matching Fabric API response"""
        return {
            "id": "folder-guid-111",
            "displayName": "Sales",
            "workspaceId": "workspace-guid-123",
        }

    @pytest.fixture
    def sample_nested_folder_data(self):
        """Sample nested folder data"""
        return {
            "id": "folder-guid-222",
            "displayName": "Y2024",
            "workspaceId": "workspace-guid-123",
            "parentFolderId": "folder-guid-111",
        }

    @pytest.fixture
    def sample_folders_list(self):
        """Sample list of folders"""
        return {
            "value": [
                {
                    "id": "folder-guid-111",
                    "displayName": "Sales",
                    "workspaceId": "workspace-guid-123",
                },
                {
                    "id": "folder-guid-222",
                    "displayName": "Y2024",
                    "workspaceId": "workspace-guid-123",
                    "parentFolderId": "folder-guid-111",
                },
                {
                    "id": "folder-guid-333",
                    "displayName": "Q1",
                    "workspaceId": "workspace-guid-123",
                    "parentFolderId": "folder-guid-222",
                },
            ]
        }

    def test_folder_from_data_no_api_call(self, mock_auth):
        """Test creating folder from pre-populated data makes no API call"""
        from fabias._fabric.folders import Folder

        client = fabias.client(auth=mock_auth)

        folder = Folder(
            client,
            "workspace-guid-123",
            folder_data={
                "id": "folder-guid-111",
                "displayName": "Sales",
                "workspaceId": "workspace-guid-123",
            },
        )

        assert folder.id == "folder-guid-111"
        assert folder.name == "Sales"
        assert folder.workspace_id == "workspace-guid-123"
        assert folder.parent is None

    def test_folder_from_data_with_parent(self, mock_auth, sample_nested_folder_data):
        """Test folder created from data preserves parent"""
        from fabias._fabric.folders import Folder

        client = fabias.client(auth=mock_auth)

        folder = Folder(client, "workspace-guid-123", folder_data=sample_nested_folder_data)

        assert folder.id == "folder-guid-222"
        assert folder.name == "Y2024"
        assert folder.parent == "folder-guid-111"

    def test_folder_by_guid_no_api_call(self, mock_auth):
        """Test that creating folder with GUID does NOT make an API call"""
        from fabias._fabric.folders import Folder

        client = fabias.client(auth=mock_auth)

        folder = Folder(client, "workspace-guid-123", "aaaaaaaa-6666-7777-8888-bbbbbbbbbbbb")

        assert folder.id == "aaaaaaaa-6666-7777-8888-bbbbbbbbbbbb"
        # Name not fetched yet (lazy)
        assert folder._data_fetched is False

    @responses.activate
    def test_folder_by_guid_lazy_loads_on_name_access(self, mock_auth, sample_folder_data):
        """Test that accessing name on GUID-created folder triggers fetch"""
        from fabias._fabric.folders import Folder

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders/aaaaaaaa-6666-7777-8888-bbbbbbbbbbbb",
            json=sample_folder_data,
            status=200,
        )

        client = fabias.client(auth=mock_auth)
        folder = Folder(client, "workspace-guid-123", "aaaaaaaa-6666-7777-8888-bbbbbbbbbbbb")

        # Access name triggers lazy load
        assert folder.name == "Sales"
        assert folder._data_fetched is True

    @responses.activate
    def test_folder_by_name_resolves(self, mock_auth, sample_folders_list):
        """Test that creating folder by name resolves via API"""
        from fabias._fabric.folders import Folder

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders",
            json=sample_folders_list,
            status=200,
        )

        client = fabias.client(auth=mock_auth)
        folder = Folder(client, "workspace-guid-123", "Sales")

        assert folder.id == "folder-guid-111"
        assert folder.name == "Sales"
        assert folder._data_fetched is True

    @responses.activate
    def test_folder_not_found_raises_error(self, mock_auth):
        """Test that looking up nonexistent folder raises NotFoundError"""
        from fabias._fabric.folders import Folder

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders",
            json={"value": []},
            status=200,
        )

        client = fabias.client(auth=mock_auth)

        with pytest.raises(NotFoundError, match="Folder 'Nonexistent' not found"):
            Folder(client, "workspace-guid-123", "Nonexistent")

    @responses.activate
    def test_folder_by_path_resolves(self, mock_auth, sample_folders_list):
        """Test resolving a folder by slash-separated path"""
        from fabias._fabric.folders import Folder

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders",
            json=sample_folders_list,
            status=200,
        )

        client = fabias.client(auth=mock_auth)
        folder = Folder(client, "workspace-guid-123", "Sales/Y2024")

        assert folder.id == "folder-guid-222"
        assert folder.name == "Y2024"
        assert folder.parent == "folder-guid-111"

    @responses.activate
    def test_folder_by_deep_path_resolves(self, mock_auth, sample_folders_list):
        """Test resolving a folder by a multi-level path"""
        from fabias._fabric.folders import Folder

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders",
            json=sample_folders_list,
            status=200,
        )

        client = fabias.client(auth=mock_auth)
        folder = Folder(client, "workspace-guid-123", "Sales/Y2024/Q1")

        assert folder.id == "folder-guid-333"
        assert folder.name == "Q1"
        assert folder.parent == "folder-guid-222"

    @responses.activate
    def test_folder_path_not_found_mid_path(self, mock_auth, sample_folders_list):
        """Test error when a segment in the path doesn't exist"""
        from fabias._fabric.folders import Folder

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders",
            json=sample_folders_list,
            status=200,
        )

        client = fabias.client(auth=mock_auth)

        with pytest.raises(NotFoundError, match="no folder named 'Nope'"):
            Folder(client, "workspace-guid-123", "Sales/Nope/Q1")

    @responses.activate
    def test_folder_ambiguous_name_shows_paths(self, mock_auth):
        """Test that duplicate folder names suggest full paths"""
        from fabias._fabric.folders import Folder

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        # Two folders named "Reports" at different levels
        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders",
            json={
                "value": [
                    {
                        "id": "folder-root",
                        "displayName": "Reports",
                        "workspaceId": "workspace-guid-123",
                    },
                    {
                        "id": "folder-sales",
                        "displayName": "Sales",
                        "workspaceId": "workspace-guid-123",
                    },
                    {
                        "id": "folder-nested",
                        "displayName": "Reports",
                        "workspaceId": "workspace-guid-123",
                        "parentFolderId": "folder-sales",
                    },
                ]
            },
            status=200,
        )

        client = fabias.client(auth=mock_auth)

        with pytest.raises(FabiasError, match="Specify the full path") as exc_info:
            Folder(client, "workspace-guid-123", "Reports")

        # Error should include path hints
        assert "Reports" in str(exc_info.value)
        assert "Sales/Reports" in str(exc_info.value)

    @responses.activate
    def test_folder_ambiguous_resolved_by_path(self, mock_auth):
        """Test that a path disambiguates duplicate folder names"""
        from fabias._fabric.folders import Folder

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders",
            json={
                "value": [
                    {
                        "id": "folder-root",
                        "displayName": "Reports",
                        "workspaceId": "workspace-guid-123",
                    },
                    {
                        "id": "folder-sales",
                        "displayName": "Sales",
                        "workspaceId": "workspace-guid-123",
                    },
                    {
                        "id": "folder-nested",
                        "displayName": "Reports",
                        "workspaceId": "workspace-guid-123",
                        "parentFolderId": "folder-sales",
                    },
                ]
            },
            status=200,
        )

        client = fabias.client(auth=mock_auth)
        folder = Folder(client, "workspace-guid-123", "Sales/Reports")

        assert folder.id == "folder-nested"
        assert folder.parent == "folder-sales"

    @responses.activate
    def test_folders_list_all(self, mock_auth, sample_folders_list):
        """Test listing all folders via accessor"""
        from fabias._fabric.folders import FolderAccessor

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders",
            json=sample_folders_list,
            status=200,
        )

        client = fabias.client(auth=mock_auth)
        accessor = FolderAccessor(client, "workspace-guid-123")

        folders = accessor()

        assert len(folders) == 3
        assert folders[0].name == "Sales"
        assert folders[1].name == "Y2024"
        assert folders[1].parent == "folder-guid-111"
        assert folders[2].name == "Q1"
        assert folders[2].parent == "folder-guid-222"

    @responses.activate
    def test_folders_list_with_root_filter(self, mock_auth):
        """Test listing folders with root_folder_id filter"""
        from fabias._fabric.folders import FolderAccessor

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders?rootFolderId=folder-guid-111&recursive=False",
            json={
                "value": [
                    {
                        "id": "folder-guid-222",
                        "displayName": "Y2024",
                        "workspaceId": "workspace-guid-123",
                        "parentFolderId": "folder-guid-111",
                    }
                ]
            },
            status=200,
        )

        client = fabias.client(auth=mock_auth)
        accessor = FolderAccessor(client, "workspace-guid-123")

        children = accessor(root_folder_id="folder-guid-111", recursive=False)

        assert len(children) == 1
        assert children[0].name == "Y2024"

    @responses.activate
    def test_folders_add_at_root(self, mock_auth):
        """Test creating a folder at workspace root"""
        from fabias._fabric.folders import FolderAccessor

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.POST,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders",
            json={
                "id": "new-folder-guid",
                "displayName": "Reports",
                "workspaceId": "workspace-guid-123",
            },
            status=201,
        )

        client = fabias.client(auth=mock_auth)
        accessor = FolderAccessor(client, "workspace-guid-123")

        folder = accessor.add("Reports")

        assert folder.id == "new-folder-guid"
        assert folder.name == "Reports"
        assert folder.parent is None

    @responses.activate
    def test_folders_add_nested(self, mock_auth):
        """Test creating a nested folder"""
        from fabias._fabric.folders import FolderAccessor

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.POST,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders",
            json={
                "id": "child-folder-guid",
                "displayName": "Q3",
                "workspaceId": "workspace-guid-123",
                "parentFolderId": "folder-guid-222",
            },
            status=201,
        )

        client = fabias.client(auth=mock_auth)
        accessor = FolderAccessor(client, "workspace-guid-123")

        folder = accessor.add("Q3", parent="folder-guid-222")

        assert folder.id == "child-folder-guid"
        assert folder.name == "Q3"
        assert folder.parent == "folder-guid-222"

    @responses.activate
    def test_folder_rename(self, mock_auth):
        """Test renaming a folder"""
        from fabias._fabric.folders import Folder

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.PATCH,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders/folder-guid-111",
            json={
                "id": "folder-guid-111",
                "displayName": "Sales Team",
                "workspaceId": "workspace-guid-123",
            },
            status=200,
        )

        client = fabias.client(auth=mock_auth)
        folder = Folder(
            client,
            "workspace-guid-123",
            folder_data={
                "id": "folder-guid-111",
                "displayName": "Sales",
                "workspaceId": "workspace-guid-123",
            },
        )

        result = folder.rename("Sales Team")

        assert result is folder  # Returns self
        assert folder.name == "Sales Team"

    @responses.activate
    def test_folder_move(self, mock_auth):
        """Test moving a folder to another parent"""
        from fabias._fabric.folders import Folder

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.POST,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders/folder-guid-222/move",
            json={
                "id": "folder-guid-222",
                "displayName": "Y2024",
                "workspaceId": "workspace-guid-123",
                "parentFolderId": "folder-guid-333",
            },
            status=200,
        )

        client = fabias.client(auth=mock_auth)
        folder = Folder(
            client,
            "workspace-guid-123",
            folder_data={
                "id": "folder-guid-222",
                "displayName": "Y2024",
                "workspaceId": "workspace-guid-123",
                "parentFolderId": "folder-guid-111",
            },
        )

        result = folder.move(target_folder_id="folder-guid-333")

        assert result is folder
        assert folder.parent == "folder-guid-333"

    @responses.activate
    def test_folder_move_to_root(self, mock_auth):
        """Test moving a folder to workspace root (no target)"""
        from fabias._fabric.folders import Folder

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.POST,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders/folder-guid-222/move",
            json={
                "id": "folder-guid-222",
                "displayName": "Y2024",
                "workspaceId": "workspace-guid-123",
            },
            status=200,
        )

        client = fabias.client(auth=mock_auth)
        folder = Folder(
            client,
            "workspace-guid-123",
            folder_data={
                "id": "folder-guid-222",
                "displayName": "Y2024",
                "workspaceId": "workspace-guid-123",
                "parentFolderId": "folder-guid-111",
            },
        )

        folder.move()

        assert folder.parent is None

    @responses.activate
    def test_folder_delete(self, mock_auth):
        """Test deleting a folder"""
        from fabias._fabric.folders import Folder

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.DELETE,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders/folder-guid-111",
            status=200,
        )

        client = fabias.client(auth=mock_auth)
        folder = Folder(
            client,
            "workspace-guid-123",
            folder_data={
                "id": "folder-guid-111",
                "displayName": "Sales",
                "workspaceId": "workspace-guid-123",
            },
        )

        folder.delete()  # Should not raise

    @responses.activate
    def test_folder_refresh(self, mock_auth):
        """Test refresh forces re-fetch from API"""
        from fabias._fabric.folders import Folder

        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders/folder-guid-111",
            json={
                "id": "folder-guid-111",
                "displayName": "Sales Updated",
                "workspaceId": "workspace-guid-123",
            },
            status=200,
        )

        client = fabias.client(auth=mock_auth)
        folder = Folder(
            client,
            "workspace-guid-123",
            folder_data={
                "id": "folder-guid-111",
                "displayName": "Sales",
                "workspaceId": "workspace-guid-123",
            },
        )

        assert folder.name == "Sales"

        result = folder.refresh()

        assert result is folder
        assert folder.name == "Sales Updated"

    def test_folder_repr(self, mock_auth):
        """Test folder __repr__"""
        from fabias._fabric.folders import Folder

        client = fabias.client(auth=mock_auth)
        folder = Folder(
            client,
            "workspace-guid-123",
            folder_data={
                "id": "folder-guid-111",
                "displayName": "Sales",
                "workspaceId": "workspace-guid-123",
            },
        )

        assert "folder-guid-111" in repr(folder)
        assert "Sales" in repr(folder)

    def test_folder_repr_not_loaded(self, mock_auth):
        """Test folder __repr__ when data not loaded"""
        from fabias._fabric.folders import Folder

        client = fabias.client(auth=mock_auth)
        folder = Folder(client, "workspace-guid-123", "aaaaaaaa-6666-7777-8888-bbbbbbbbbbbb")

        assert "(not loaded)" in repr(folder)

    def test_folder_requires_identifier_or_data(self, mock_auth):
        """Test that Folder raises ValueError without identifier or data"""
        from fabias._fabric.folders import Folder

        client = fabias.client(auth=mock_auth)

        with pytest.raises(ValueError, match="Either identifier or folder_data"):
            Folder(client, "workspace-guid-123")

    @responses.activate
    def test_workspace_folder_method(self, mock_auth, sample_folders_list):
        """Test workspace.folder() accessor"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        # Mock workspace resolution
        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces",
            json={
                "value": [
                    {
                        "id": "workspace-guid-123",
                        "displayName": "TestWorkspace",
                        "capacityId": "cap-1",
                    }
                ]
            },
            status=200,
        )

        # Mock folder list for name resolution
        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders",
            json=sample_folders_list,
            status=200,
        )

        fabias.client(auth=mock_auth)
        ws = fabias.workspace("TestWorkspace")

        folder = ws.folder("Sales")

        assert folder.id == "folder-guid-111"
        assert folder.name == "Sales"

    @responses.activate
    def test_workspace_folders_property(self, mock_auth, sample_folders_list):
        """Test workspace.folders() property returns FolderAccessor results"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token",
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

        # Mock workspace resolution
        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces",
            json={
                "value": [
                    {
                        "id": "workspace-guid-123",
                        "displayName": "TestWorkspace",
                        "capacityId": "cap-1",
                    }
                ]
            },
            status=200,
        )

        # Mock folder listing
        responses.add(
            responses.GET,
            "https://api.fabric.microsoft.com/v1/workspaces/workspace-guid-123/folders",
            json=sample_folders_list,
            status=200,
        )

        fabias.client(auth=mock_auth)
        ws = fabias.workspace("TestWorkspace")

        folders = ws.folders()

        assert len(folders) == 3
        assert folders[0].name == "Sales"

    def test_folder_class_importable_from_fabias(self):
        """Folder is importable directly from fabias."""
        import fabias

        F = fabias.Folder
        assert F is not None

        from fabias._fabric.folders import Folder

        assert F is Folder


# =============================================================================
# SPARK SETTINGS TESTS
# =============================================================================

# =============================================================================
# SPARK SETTINGS TESTS
# =============================================================================


class TestSparkSettings:
    """Tests for workspace Spark settings - mutable-property + commit() API."""

    WS_ID = "ws-spark-guid-123"
    BASE_URL = "https://api.fabric.microsoft.com/v1"
    TOKEN_URL = "https://login.microsoftonline.com/fake-tenant-id/oauth2/v2.0/token"
    SETTINGS_URL = f"{BASE_URL}/workspaces/{WS_ID}/spark/settings"

    def _token_mock(self):
        responses.add(
            responses.POST,
            self.TOKEN_URL,
            json={"access_token": "fake-token", "expires_in": 3600},
            status=200,
        )

    def _ws(self):
        from fabias._fabric.workspace import Workspace

        return Workspace(
            fabias._fabric._client, workspace_data={"id": self.WS_ID, "displayName": "Spark WS"}
        )

    def _full_payload(self):
        return {
            "automaticLog": {"enabled": True},
            "highConcurrency": {
                "notebookInteractiveRunEnabled": True,
                "notebookPipelineRunEnabled": False,
            },
            "environment": {"name": "ML Environment", "runtimeVersion": "1.3"},
            "pool": {
                "customizeComputeEnabled": True,
                "defaultPool": {"name": "Starter Pool", "type": "Workspace"},
                "starterPool": {"maxNodeCount": 10, "maxExecutors": 5},
            },
            "job": {
                "conservativeJobAdmissionEnabled": True,
                "sessionTimeoutInMinutes": 30,
            },
        }

    # -------------------------------------------------------------------------
    # HighConcurrencySettings
    # -------------------------------------------------------------------------

    def test_high_concurrency_from_json(self):
        from fabias._fabric.spark import HighConcurrencySettings

        hc = HighConcurrencySettings.fromJson(
            {"notebookInteractiveRunEnabled": True, "notebookPipelineRunEnabled": False}
        )
        assert hc.interactive is True
        assert hc.pipelines is False

    def test_high_concurrency_from_json_absent_fields(self):
        from fabias._fabric.spark import HighConcurrencySettings

        hc = HighConcurrencySettings.fromJson({})
        assert hc.interactive is None
        assert hc.pipelines is None

    def test_high_concurrency_to_json_omits_none(self):
        from fabias._fabric.spark import HighConcurrencySettings

        j = HighConcurrencySettings().toJson()
        assert j == {}

    def test_high_concurrency_to_json_with_values(self):
        from fabias._fabric.spark import HighConcurrencySettings

        j = HighConcurrencySettings(interactive=True, pipelines=False).toJson()
        assert j == {"notebookInteractiveRunEnabled": True, "notebookPipelineRunEnabled": False}

    # -------------------------------------------------------------------------
    # SparkEnvironment / Environment alias
    # -------------------------------------------------------------------------

    def test_environment_from_json(self):
        from fabias._fabric.spark import SparkEnvironment

        env = SparkEnvironment.fromJson({"name": "ML Env", "runtimeVersion": "1.3"})
        assert env.name == "ML Env"
        assert env.version == "1.3"

    def test_environment_from_json_empty(self):
        from fabias._fabric.spark import SparkEnvironment

        env = SparkEnvironment.fromJson({})
        assert env.name is None
        assert env.version is None

    def test_environment_to_json_omits_none(self):
        from fabias._fabric.spark import SparkEnvironment

        assert SparkEnvironment(name="ML").toJson() == {"name": "ML"}
        assert SparkEnvironment().toJson() == {}

    def test_environment_to_json_full(self):
        from fabias._fabric.spark import SparkEnvironment

        j = SparkEnvironment(name="ML", version="1.3").toJson()
        assert j == {"name": "ML", "runtimeVersion": "1.3"}

    def test_environment_alias(self):
        from fabias._fabric.spark import Environment, SparkEnvironment

        assert Environment is SparkEnvironment

    # -------------------------------------------------------------------------
    # JobSettings
    # -------------------------------------------------------------------------

    def test_job_settings_from_json_amount_of_cores(self):
        from fabias._fabric.spark import JobSettings

        js = JobSettings.fromJson(
            {"conservativeJobAdmissionEnabled": True, "sessionTimeoutInMinutes": 30}
        )
        assert js.reservecores is True
        assert js.timeout == 30

    def test_job_settings_from_json_driver(self):
        from fabias._fabric.spark import JobSettings

        js = JobSettings.fromJson({"conservativeJobAdmissionEnabled": False})
        assert js.reservecores is False
        assert js.timeout is None

    def test_job_settings_from_json_absent(self):
        from fabias._fabric.spark import JobSettings

        js = JobSettings.fromJson({})
        assert js.reservecores is None
        assert js.timeout is None

    def test_job_settings_to_json_reservecores_true(self):
        from fabias._fabric.spark import JobSettings

        j = JobSettings(reservecores=True, timeout=15).toJson()
        assert j == {"conservativeJobAdmissionEnabled": True, "sessionTimeoutInMinutes": 15}

    def test_job_settings_to_json_reservecores_false(self):
        from fabias._fabric.spark import JobSettings

        j = JobSettings(reservecores=False).toJson()
        assert j == {"conservativeJobAdmissionEnabled": False}

    def test_job_settings_to_json_omits_none(self):
        from fabias._fabric.spark import JobSettings

        assert JobSettings().toJson() == {}

    # -------------------------------------------------------------------------
    # DefaultPoolSettings / StarterPoolSettings / PoolSettings
    # -------------------------------------------------------------------------

    def test_default_pool_from_json(self):
        from fabias._fabric.spark import DefaultPoolSettings

        dp = DefaultPoolSettings.fromJson({"name": "Starter Pool", "type": "Workspace"})
        assert dp.name == "Starter Pool"
        assert dp.type == "Workspace"

    def test_default_pool_to_json_with_enum(self):
        from fabias._fabric._shared.enums import PoolType
        from fabias._fabric.spark import DefaultPoolSettings

        j = DefaultPoolSettings(name="P", type=PoolType.CAPACITY).toJson()
        assert j == {"name": "P", "type": "Capacity"}

    def test_default_pool_to_json_omits_none(self):
        from fabias._fabric.spark import DefaultPoolSettings

        assert DefaultPoolSettings().toJson() == {}

    def test_starter_pool_from_json(self):
        from fabias._fabric.spark import StarterPoolSettings

        sp = StarterPoolSettings.fromJson({"maxNodeCount": 10, "maxExecutors": 5})
        assert sp.nodes == 10
        assert sp.executors == 5

    def test_starter_pool_to_json(self):
        from fabias._fabric.spark import StarterPoolSettings

        j = StarterPoolSettings(nodes=8, executors=4).toJson()
        assert j == {"maxNodeCount": 8, "maxExecutors": 4}

    def test_starter_pool_to_json_omits_none(self):
        from fabias._fabric.spark import StarterPoolSettings

        assert StarterPoolSettings(nodes=3).toJson() == {"maxNodeCount": 3}

    def test_pool_settings_from_json(self):
        from fabias._fabric.spark import DefaultPoolSettings, PoolSettings, StarterPoolSettings

        data = {
            "customizeComputeEnabled": True,
            "defaultPool": {"name": "Starter Pool", "type": "Workspace"},
            "starterPool": {"maxNodeCount": 10, "maxExecutors": 5},
        }
        pool = PoolSettings.fromJson(data)
        assert pool.customcompute is True
        assert isinstance(pool.default, DefaultPoolSettings)
        assert pool.default.name == "Starter Pool"
        assert isinstance(pool.starter, StarterPoolSettings)
        assert pool.starter.nodes == 10
        assert pool.starter.executors == 5

    def test_pool_settings_from_json_empty(self):
        from fabias._fabric.spark import PoolSettings

        pool = PoolSettings.fromJson({})
        assert pool.customcompute is None
        assert pool.default.name is None
        assert pool.starter.nodes is None

    def test_pool_settings_to_json(self):
        from fabias._fabric.spark import DefaultPoolSettings, PoolSettings, StarterPoolSettings

        pool = PoolSettings(
            customcompute=True,
            default=DefaultPoolSettings(name="X", type="Workspace"),
            starter=StarterPoolSettings(nodes=5, executors=2),
        )
        j = pool.toJson()
        assert j["customizeComputeEnabled"] is True
        assert j["defaultPool"] == {"name": "X", "type": "Workspace"}
        assert j["starterPool"] == {"maxNodeCount": 5, "maxExecutors": 2}

    def test_pool_settings_to_json_omits_empty_sub_pools(self):
        from fabias._fabric.spark import PoolSettings

        j = PoolSettings(customcompute=False).toJson()
        assert j == {"customizeComputeEnabled": False}
        assert "defaultPool" not in j
        assert "starterPool" not in j

    # -------------------------------------------------------------------------
    # SparkSettings container
    # -------------------------------------------------------------------------

    def test_spark_settings_hydrates_all_sections(self, mock_auth):
        from fabias._fabric.spark import (
            HighConcurrencySettings,
            JobSettings,
            PoolSettings,
            SparkEnvironment,
            SparkSettings,
        )

        fabias.client(auth=mock_auth)
        settings = SparkSettings(fabias._fabric._client, self.WS_ID, self._full_payload())

        assert settings.log is True
        assert isinstance(settings.highconcurrency, HighConcurrencySettings)
        assert settings.highconcurrency.interactive is True
        assert settings.highconcurrency.pipelines is False
        assert isinstance(settings.environment, SparkEnvironment)
        assert settings.environment.name == "ML Environment"
        assert settings.environment.version == "1.3"
        assert isinstance(settings.pool, PoolSettings)
        assert settings.pool.customcompute is True
        assert settings.pool.default.name == "Starter Pool"
        assert settings.pool.starter.nodes == 10
        assert settings.pool.starter.executors == 5
        assert isinstance(settings.jobs, JobSettings)
        assert settings.jobs.reservecores is True
        assert settings.jobs.timeout == 30

    def test_spark_settings_hydrates_empty_payload(self, mock_auth):
        from fabias._fabric.spark import SparkSettings

        fabias.client(auth=mock_auth)
        settings = SparkSettings(fabias._fabric._client, self.WS_ID, {})
        assert settings.log is None
        assert settings.highconcurrency.interactive is None
        assert settings.environment.name is None
        assert settings.pool.customcompute is None
        assert settings.jobs.reservecores is None

    def test_spark_settings_to_json_full(self, mock_auth):
        from fabias._fabric.spark import SparkSettings

        fabias.client(auth=mock_auth)
        settings = SparkSettings(fabias._fabric._client, self.WS_ID, self._full_payload())
        j = settings._toJson()
        assert j["automaticLog"] == {"enabled": True}
        assert j["highConcurrency"]["notebookInteractiveRunEnabled"] is True
        assert j["highConcurrency"]["notebookPipelineRunEnabled"] is False
        assert j["environment"]["name"] == "ML Environment"
        assert j["environment"]["runtimeVersion"] == "1.3"
        assert j["pool"]["customizeComputeEnabled"] is True
        assert j["pool"]["defaultPool"]["name"] == "Starter Pool"
        assert j["pool"]["starterPool"]["maxNodeCount"] == 10
        assert j["job"]["conservativeJobAdmissionEnabled"] is True
        assert j["job"]["sessionTimeoutInMinutes"] == 30

    def test_spark_settings_to_json_omits_none_log(self, mock_auth):
        from fabias._fabric.spark import SparkSettings

        fabias.client(auth=mock_auth)
        settings = SparkSettings(fabias._fabric._client, self.WS_ID, {})
        j = settings._toJson()
        assert "automaticLog" not in j

    def test_spark_settings_mutable_log(self, mock_auth):
        from fabias._fabric.spark import SparkSettings

        fabias.client(auth=mock_auth)
        settings = SparkSettings(
            fabias._fabric._client, self.WS_ID, {"automaticLog": {"enabled": False}}
        )
        settings.log = True
        assert settings._toJson()["automaticLog"]["enabled"] is True

    def test_spark_settings_mutable_sub_object(self, mock_auth):
        from fabias._fabric.spark import SparkSettings

        fabias.client(auth=mock_auth)
        settings = SparkSettings(fabias._fabric._client, self.WS_ID, self._full_payload())
        settings.pool.starter.nodes = 99
        j = settings._toJson()
        assert j["pool"]["starterPool"]["maxNodeCount"] == 99

    def test_spark_settings_environment_replacement(self, mock_auth):
        from fabias._fabric.spark import Environment, SparkSettings

        fabias.client(auth=mock_auth)
        settings = SparkSettings(fabias._fabric._client, self.WS_ID, {})
        settings.environment = Environment("New Env", "1.3")
        j = settings._toJson()
        assert j["environment"] == {"name": "New Env", "runtimeVersion": "1.3"}

    def test_spark_settings_repr(self, mock_auth):
        from fabias._fabric.spark import SparkSettings

        fabias.client(auth=mock_auth)
        settings = SparkSettings(fabias._fabric._client, self.WS_ID, self._full_payload())
        r = repr(settings)
        assert "log=True" in r
        assert "ML Environment" in r
        assert "Starter Pool" in r

    # -------------------------------------------------------------------------
    # workspace.spark property and settings caching
    # -------------------------------------------------------------------------

    def test_workspace_spark_property_returns_spark_handler(self, mock_auth):
        from fabias._fabric.spark import Spark

        fabias.client(auth=mock_auth)
        ws = self._ws()
        assert isinstance(ws.spark, Spark)

    def test_workspace_spark_is_cached(self, mock_auth):
        fabias.client(auth=mock_auth)
        ws = self._ws()
        assert ws.spark is ws.spark

    @responses.activate
    def test_workspace_spark_settings_is_property_returning_spark_settings(self, mock_auth):
        from fabias._fabric.spark import SparkSettings

        self._token_mock()
        responses.add(responses.GET, self.SETTINGS_URL, json=self._full_payload(), status=200)
        fabias.client(auth=mock_auth)
        ws = self._ws()
        s = ws.spark.settings
        assert isinstance(s, SparkSettings)

    @responses.activate
    def test_workspace_spark_settings_is_cached(self, mock_auth):
        """workspace.spark.settings returns same object on repeated access (lazy-load once)."""
        self._token_mock()
        responses.add(responses.GET, self.SETTINGS_URL, json=self._full_payload(), status=200)
        fabias.client(auth=mock_auth)
        ws = self._ws()
        s1 = ws.spark.settings
        s2 = ws.spark.settings
        assert s1 is s2
        # Only one GET token + one GET settings = 2 HTTP calls total
        assert len(responses.calls) == 2

    @responses.activate
    def test_workspace_spark_settings_correct_endpoint(self, mock_auth):
        self._token_mock()
        responses.add(responses.GET, self.SETTINGS_URL, json=self._full_payload(), status=200)
        fabias.client(auth=mock_auth)
        ws = self._ws()
        ws.spark.settings
        get_call = next(c for c in responses.calls if c.request.method == "GET")
        assert f"/workspaces/{self.WS_ID}/spark/settings" in get_call.request.url

    # -------------------------------------------------------------------------
    # commit()
    # -------------------------------------------------------------------------

    @responses.activate
    def test_commit_patches_and_refreshes(self, mock_auth):
        """commit() sends PATCH then GET, and hydrates settings from GET response."""
        import json as _json

        self._token_mock()
        responses.add(responses.GET, self.SETTINGS_URL, json=self._full_payload(), status=200)
        responses.add(responses.PATCH, self.SETTINGS_URL, json={}, status=200)
        updated = {**self._full_payload(), "automaticLog": {"enabled": True}}
        responses.add(responses.GET, self.SETTINGS_URL, json=updated, status=200)

        fabias.client(auth=mock_auth)
        ws = self._ws()
        settings = ws.spark.settings  # GET #1
        settings.log = True
        result = settings.commit()  # PATCH + GET #2

        patch_call = next(c for c in responses.calls if c.request.method == "PATCH")
        body = _json.loads(patch_call.request.body)
        assert body["automaticLog"]["enabled"] is True
        assert result is settings  # returns self
        assert settings.log is True

    @responses.activate
    def test_commit_sends_full_current_state(self, mock_auth):
        """commit() serialises all non-None settings, not just changed ones."""
        import json as _json

        self._token_mock()
        responses.add(responses.GET, self.SETTINGS_URL, json=self._full_payload(), status=200)
        responses.add(responses.PATCH, self.SETTINGS_URL, json={}, status=200)
        responses.add(responses.GET, self.SETTINGS_URL, json=self._full_payload(), status=200)

        fabias.client(auth=mock_auth)
        ws = self._ws()
        settings = ws.spark.settings
        settings.pool.starter.nodes = 20
        settings.commit()

        patch_call = next(c for c in responses.calls if c.request.method == "PATCH")
        body = _json.loads(patch_call.request.body)
        assert body["pool"]["starterPool"]["maxNodeCount"] == 20
        assert "automaticLog" in body  # all non-None sections sent
        assert "highConcurrency" in body

    @responses.activate
    def test_commit_pool_type_enum_serialised(self, mock_auth):
        """commit() serialises PoolType enum to string in PATCH body."""
        import json as _json

        self._token_mock()
        responses.add(responses.GET, self.SETTINGS_URL, json={}, status=200)
        responses.add(responses.PATCH, self.SETTINGS_URL, json={}, status=200)
        responses.add(responses.GET, self.SETTINGS_URL, json={}, status=200)

        from fabias._fabric._shared.enums import PoolType

        fabias.client(auth=mock_auth)
        ws = self._ws()
        settings = ws.spark.settings
        settings.pool.default.name = "My Pool"
        settings.pool.default.type = PoolType.CAPACITY
        settings.commit()

        patch_call = next(c for c in responses.calls if c.request.method == "PATCH")
        body = _json.loads(patch_call.request.body)
        assert body["pool"]["defaultPool"]["type"] == "Capacity"

    @responses.activate
    def test_commit_jobs_reservecores_true_serialises_amount_of_cores(self, mock_auth):
        import json as _json

        self._token_mock()
        responses.add(responses.GET, self.SETTINGS_URL, json={}, status=200)
        responses.add(responses.PATCH, self.SETTINGS_URL, json={}, status=200)
        responses.add(responses.GET, self.SETTINGS_URL, json={}, status=200)

        fabias.client(auth=mock_auth)
        ws = self._ws()
        settings = ws.spark.settings
        settings.jobs.reservecores = True
        settings.jobs.timeout = 45
        settings.commit()

        patch_call = next(c for c in responses.calls if c.request.method == "PATCH")
        body = _json.loads(patch_call.request.body)
        assert body["job"]["conservativeJobAdmissionEnabled"] is True
        assert body["job"]["sessionTimeoutInMinutes"] == 45

    @responses.activate
    def test_commit_jobs_reservecores_false_serialises_driver(self, mock_auth):
        import json as _json

        self._token_mock()
        responses.add(responses.GET, self.SETTINGS_URL, json={}, status=200)
        responses.add(responses.PATCH, self.SETTINGS_URL, json={}, status=200)
        responses.add(responses.GET, self.SETTINGS_URL, json={}, status=200)

        fabias.client(auth=mock_auth)
        ws = self._ws()
        settings = ws.spark.settings
        settings.jobs.reservecores = False
        settings.commit()

        patch_call = next(c for c in responses.calls if c.request.method == "PATCH")
        body = _json.loads(patch_call.request.body)
        assert body["job"]["conservativeJobAdmissionEnabled"] is False

    # -------------------------------------------------------------------------
    # PoolType enum
    # -------------------------------------------------------------------------

    def test_pool_type_enum_values(self):
        from fabias._fabric._shared.enums import PoolType

        assert PoolType.WORKSPACE.value == "Workspace"
        assert PoolType.CAPACITY.value == "Capacity"

    def test_pool_type_importable_from_fabric(self):
        from fabias._fabric._shared.enums import PoolType

        assert PoolType is not None
