"""Tests for entity visibility WITHOUT a magic workspace (ADR-003).

There is no longer a ``source`` provenance column on any table: the base
WorkspaceScopedRepository read filter is pure workspace scoping
(``workspace_id IN accessible``). Built-in content lives in the registry catalog
and is read globally by the catalog repositories, not by this filter. A row from
another workspace always stays hidden.
"""
from unittest.mock import AsyncMock, MagicMock
from uuid import uuid4

import pytest
from agentarea_common.auth.context import UserContext
from agentarea_common.auth.simple_authorization import SimpleAuthorizationService
from agentarea_llm.domain.models import ModelInstance, ProviderConfig
from agentarea_llm.infrastructure.model_instance_repository import ModelInstanceRepository
from agentarea_llm.infrastructure.model_spec_repository import ModelSpecRepository
from agentarea_llm.infrastructure.provider_config_repository import ProviderConfigRepository


def _user_context_with_system(workspace_id: str = "ws-1") -> UserContext:
    """UserContext for a user in a single tenant workspace (no platform sentinel)."""
    return UserContext(
        user_id="user-1",
        workspace_id=workspace_id,
        accessible_workspaces=[workspace_id],
    )


def test_model_spec_filter_is_pure_workspace_scoping():
    """ModelSpec has NO source column anymore: the read filter is pure workspace
    scoping. Built-in specs are read from the registry catalog, not here."""
    session = MagicMock()
    user_context = _user_context_with_system()
    repo = ModelSpecRepository(session, user_context)
    ws_filter = repo._get_workspace_filter()
    compiled = str(ws_filter.compile(compile_kwargs={"literal_binds": True}))
    assert "ws-1" in compiled
    assert "official" not in compiled
    assert "platform" not in compiled


def test_provider_config_filter_is_pure_workspace_scoping():
    """ProviderConfig no longer carries `source`; pure workspace scoping."""
    session = MagicMock()
    user_context = _user_context_with_system()
    repo = ProviderConfigRepository(session, user_context)
    ws_filter = repo._get_workspace_filter()
    compiled = str(ws_filter.compile(compile_kwargs={"literal_binds": True}))
    assert "ws-1" in compiled
    assert "official" not in compiled


def test_model_instance_filter_has_no_source_predicate():
    """ModelInstance is purely workspace-scoped, so a row from another workspace
    must never leak in."""
    session = MagicMock()
    user_context = _user_context_with_system()
    repo = ModelInstanceRepository(session, user_context)
    ws_filter = repo._get_workspace_filter()
    compiled = str(ws_filter.compile(compile_kwargs={"literal_binds": True}))
    assert "ws-1" in compiled
    assert "official" not in compiled


def test_no_filter_enumerates_foreign_or_official_rows():
    """No source OR-branch: visibility is own-workspace only. Neither another
    workspace nor an 'official' escape hatch appears in the compiled filter."""
    session = MagicMock()
    user_context = _user_context_with_system("ws-1")
    repo = ModelSpecRepository(session, user_context)
    ws_filter = repo._get_workspace_filter()
    compiled = str(ws_filter.compile(compile_kwargs={"literal_binds": True}))
    assert "ws-2" not in compiled
    assert "workspace_custom" not in compiled
    assert "official" not in compiled


@pytest.mark.asyncio
async def test_provider_config_repo_create_scopes_to_current_workspace(monkeypatch):
    session = MagicMock()
    session.commit = AsyncMock()
    user_context = _user_context_with_system()
    repo = ProviderConfigRepository(session, user_context)
    monkeypatch.setattr(repo, "get_with_relations", AsyncMock(return_value=None))

    # Pre-populate workspace_id AND created_by with attacker-controlled values
    # to prove the repo unconditionally overwrites both from UserContext (no
    # silent fallback — see project rule "never fall back to system user_id").
    config = ProviderConfig(
        provider_spec_id=uuid4(),
        name="Test provider",
        api_key="secret-ref",
        workspace_id="wrong-workspace",
        created_by="attacker-user",
    )

    created = await repo.create_config(config)

    assert created is config
    assert config.workspace_id == "ws-1"
    assert config.created_by == "user-1"
    session.add.assert_called_once_with(config)
    session.commit.assert_awaited_once()


@pytest.mark.asyncio
async def test_provider_config_repo_has_update_config(monkeypatch):
    """Regression: ProviderService.update_provider_config calls
    repo.update_config(config). Missing method raised AttributeError on every
    PUT/PATCH /provider-configs/{id} and the providers_toolset update path.
    The method must delegate to the base update_from_entity contract
    (mutated domain object → persisted + refreshed record).
    """
    session = MagicMock()
    user_context = _user_context_with_system()
    repo = ProviderConfigRepository(session, user_context)

    config = ProviderConfig(
        id=uuid4(),
        provider_spec_id=uuid4(),
        name="Renamed provider",
        api_key="secret-ref",
        workspace_id="ws-1",
        created_by="user-1",
    )

    update_from_entity_mock = AsyncMock(return_value=config)
    monkeypatch.setattr(repo, "update_from_entity", update_from_entity_mock)
    monkeypatch.setattr(repo, "get_with_relations", AsyncMock(return_value=config))

    result = await repo.update_config(config)

    update_from_entity_mock.assert_awaited_once_with(config)
    assert result is config


@pytest.mark.asyncio
async def test_model_instance_repo_create_scopes_to_current_workspace(monkeypatch):
    session = MagicMock()
    session.commit = AsyncMock()
    user_context = _user_context_with_system()
    repo = ModelInstanceRepository(session, user_context)
    monkeypatch.setattr(repo, "get_with_relations", AsyncMock(return_value=None))

    # Same overwrite contract as ProviderConfig — supplied created_by must be
    # ignored and replaced with the calling UserContext.user_id.
    instance = ModelInstance(
        provider_config_id=uuid4(),
        model_spec_id=uuid4(),
        name="Test model",
        workspace_id="wrong-workspace",
        created_by="attacker-user",
    )

    created = await repo.create_instance(instance)

    assert created is instance
    assert instance.workspace_id == "ws-1"
    assert instance.created_by == "user-1"
    session.add.assert_called_once_with(instance)
    session.commit.assert_awaited_once()


def test_default_user_context_only_own_workspace():
    """Without AuthorizationService, UserContext defaults to own workspace only."""
    user_context = UserContext(user_id="user-1", workspace_id="ws-1")
    assert user_context.accessible_workspaces == ["ws-1"]


@pytest.mark.asyncio
async def test_simple_authorization_grants_only_own_workspace():
    """SimpleAuthorizationService grants access to the user's own workspace only.

    Built-in/official content is no longer surfaced by injecting a magic
    'platform' workspace here — it is globally readable by provenance instead.
    """
    authz = SimpleAuthorizationService()
    user_context = UserContext(user_id="user-1", workspace_id="ws-1")
    workspaces = await authz.get_accessible_workspaces(user_context)
    assert workspaces == ["ws-1"]
    assert "platform" not in workspaces


@pytest.mark.asyncio
async def test_simple_authorization_write_own_workspace():
    """SimpleAuthorizationService allows writes only to own workspace."""
    authz = SimpleAuthorizationService()
    user_context = UserContext(user_id="user-1", workspace_id="ws-1")
    assert await authz.can_write_workspace(user_context, "ws-1") is True
    assert await authz.can_write_workspace(user_context, "system") is False
    assert await authz.can_write_workspace(user_context, "other-ws") is False
