"""
P-04: Copyright-Pflicht bei Promotion auf club/official.
PATCH /api/media-assets/{id} und POST /api/media-assets/bulk-patch
lehnen eine Sichtbarkeits-Promotion auf club oder official ab,
wenn keine copyright_notice vorhanden ist.
"""
from __future__ import annotations

import os
from contextlib import ExitStack
from unittest.mock import MagicMock, patch

import pytest
from fastapi.testclient import TestClient

os.environ.setdefault("SKIP_DB_MIGRATE", "1")

from main import app
from tenant_context import TenantContext, get_tenant_context

_SUPERADMIN_TENANT = TenantContext(
    profile_id=1,
    global_role="superadmin",
    effective_club_id=None,
    club_ids=frozenset(),
    memberships=[],
)

_PRIVATE_ASSET: dict = {
    "id": 42,
    "visibility": "private",
    "club_id": 7,
    "uploaded_by_profile_id": 1,
    "lifecycle_state": "active",
    "copyright_notice": None,
    "original_filename": "foto.jpg",
    "sha256": "a" * 64,
    "storage_key": f"library/verein-7/image/{'a' * 64}.jpg",
    "storage_backend": "local",
    "mime_type": "image/jpeg",
    "byte_size": 1024,
    "created_at": None,
    "tags": [],
}

_ASSET_WITH_COPYRIGHT: dict = {**_PRIVATE_ASSET, "copyright_notice": "Verein 2026"}


@pytest.fixture
def client() -> TestClient:
    return TestClient(app)


@pytest.fixture(autouse=True)
def _clear_overrides():
    yield
    app.dependency_overrides.pop(get_tenant_context, None)


def _make_db_mocks(asset: dict) -> tuple[MagicMock, MagicMock]:
    mock_cur = MagicMock()
    mock_cur.fetchone.return_value = asset
    mock_conn = MagicMock()
    mock_cm = MagicMock()
    mock_cm.__enter__.return_value = mock_conn
    mock_cm.__exit__.return_value = False
    return mock_cm, mock_cur


_PERMISSION_PATCHES = [
    ("routers.media_assets.assert_can_edit_media_asset_metadata", {}),
    ("routers.media_assets.assert_valid_governance_visibility", {}),
    ("routers.media_assets._media_assets_tags_column_present", {"return_value": False}),
    ("routers.media_assets.get_effective_media_root", {"return_value": "/tmp/media"}),
    ("routers.media_assets._relocate_asset_file_if_governance_changed", {"return_value": None}),
    # P-06: bestehende Tests testen Copyright, nicht Rechteerklaerung – "ok" mocken
    ("routers.media_assets.check_rights_coverage", {"return_value": "ok"}),
]


def _enter_permission_patches(stack: ExitStack) -> None:
    for target, kwargs in _PERMISSION_PATCHES:
        stack.enter_context(patch(target, **kwargs))


# ── Single PATCH ─────────────────────────────────────────────────────────────


def test_patch_promote_to_club_without_copyright_returns_400(client: TestClient) -> None:
    """Promotion private -> club ohne copyright_notice muss 400 liefern."""
    app.dependency_overrides[get_tenant_context] = lambda: _SUPERADMIN_TENANT
    mock_cm, mock_cur = _make_db_mocks(_PRIVATE_ASSET)

    with ExitStack() as stack:
        stack.enter_context(patch("routers.media_assets.get_db", return_value=mock_cm))
        stack.enter_context(patch("routers.media_assets.get_cursor", return_value=mock_cur))
        _enter_permission_patches(stack)
        r = client.patch(
            "/api/media-assets/42",
            json={"visibility": "club", "club_id": 7},
            headers={"X-Auth-Token": "t"},
        )

    assert r.status_code == 400
    detail = r.json()["detail"].lower()
    assert "copyright" in detail or "urheberrecht" in detail


def test_patch_promote_to_official_without_copyright_returns_400(client: TestClient) -> None:
    """Promotion private -> official ohne copyright_notice muss 400 liefern."""
    app.dependency_overrides[get_tenant_context] = lambda: _SUPERADMIN_TENANT
    mock_cm, mock_cur = _make_db_mocks(_PRIVATE_ASSET)

    with ExitStack() as stack:
        stack.enter_context(patch("routers.media_assets.get_db", return_value=mock_cm))
        stack.enter_context(patch("routers.media_assets.get_cursor", return_value=mock_cur))
        _enter_permission_patches(stack)
        r = client.patch(
            "/api/media-assets/42",
            json={"visibility": "official"},
            headers={"X-Auth-Token": "t"},
        )

    assert r.status_code == 400
    detail = r.json()["detail"].lower()
    assert "copyright" in detail or "urheberrecht" in detail


def test_patch_promote_to_club_with_copyright_in_body_allowed(client: TestClient) -> None:
    """Promotion private -> club MIT copyright_notice im Body muss 200 liefern."""
    app.dependency_overrides[get_tenant_context] = lambda: _SUPERADMIN_TENANT
    mock_cm, mock_cur = _make_db_mocks(_PRIVATE_ASSET)
    updated_asset = {**_PRIVATE_ASSET, "visibility": "club", "copyright_notice": "Verein 2026"}
    mock_cur.fetchone.side_effect = [_PRIVATE_ASSET, updated_asset]

    with ExitStack() as stack:
        stack.enter_context(patch("routers.media_assets.get_db", return_value=mock_cm))
        stack.enter_context(patch("routers.media_assets.get_cursor", return_value=mock_cur))
        _enter_permission_patches(stack)
        r = client.patch(
            "/api/media-assets/42",
            json={"visibility": "club", "club_id": 7, "copyright_notice": "Verein 2026"},
            headers={"X-Auth-Token": "t"},
        )

    assert r.status_code == 200
    body = r.json()
    assert body["id"] == 42
    assert body["visibility"] == "club"
    assert body["copyright_notice"] == "Verein 2026"


def test_patch_promote_to_club_existing_copyright_allowed(client: TestClient) -> None:
    """Asset hat bereits copyright_notice -> Promotion ohne Body-Copyright muss 200 liefern."""
    app.dependency_overrides[get_tenant_context] = lambda: _SUPERADMIN_TENANT
    promoted_asset = {**_ASSET_WITH_COPYRIGHT, "visibility": "club"}
    mock_cm, mock_cur = _make_db_mocks(_ASSET_WITH_COPYRIGHT)
    mock_cur.fetchone.side_effect = [_ASSET_WITH_COPYRIGHT, promoted_asset]

    with ExitStack() as stack:
        stack.enter_context(patch("routers.media_assets.get_db", return_value=mock_cm))
        stack.enter_context(patch("routers.media_assets.get_cursor", return_value=mock_cur))
        _enter_permission_patches(stack)
        r = client.patch(
            "/api/media-assets/42",
            json={"visibility": "club", "club_id": 7},
            headers={"X-Auth-Token": "t"},
        )

    assert r.status_code == 200
    body = r.json()
    assert body["id"] == 42
    assert body["visibility"] == "club"
    assert body["copyright_notice"] == "Verein 2026"


def test_patch_filename_only_no_copyright_check(client: TestClient) -> None:
    """Kein Visibility-Wechsel -> keine Copyright-Prufung."""
    app.dependency_overrides[get_tenant_context] = lambda: _SUPERADMIN_TENANT
    mock_cm, mock_cur = _make_db_mocks(_PRIVATE_ASSET)
    mock_cur.fetchone.side_effect = [
        _PRIVATE_ASSET,
        {**_PRIVATE_ASSET, "original_filename": "neu.jpg"},
    ]

    with ExitStack() as stack:
        stack.enter_context(patch("routers.media_assets.get_db", return_value=mock_cm))
        stack.enter_context(patch("routers.media_assets.get_cursor", return_value=mock_cur))
        _enter_permission_patches(stack)
        r = client.patch(
            "/api/media-assets/42",
            json={"original_filename": "neu.jpg"},
            headers={"X-Auth-Token": "t"},
        )

    assert r.status_code == 200


# ── Bulk PATCH ────────────────────────────────────────────────────────────────


def test_bulk_patch_promote_to_club_without_copyright_in_failed(client: TestClient) -> None:
    """Bulk-Promotion ohne copyright_notice -> Asset in failed-Liste."""
    app.dependency_overrides[get_tenant_context] = lambda: _SUPERADMIN_TENANT
    mock_cur = MagicMock()
    mock_cur.fetchone.return_value = _PRIVATE_ASSET
    mock_conn = MagicMock()
    mock_cm = MagicMock()
    mock_cm.__enter__.return_value = mock_conn
    mock_cm.__exit__.return_value = False

    with ExitStack() as stack:
        stack.enter_context(patch("routers.media_assets.get_db", return_value=mock_cm))
        stack.enter_context(patch("routers.media_assets.get_cursor", return_value=mock_cur))
        _enter_permission_patches(stack)
        r = client.post(
            "/api/media-assets/bulk-patch",
            json={"media_asset_ids": [42], "visibility": "club", "club_id": 7},
            headers={"X-Auth-Token": "t"},
        )

    assert r.status_code == 200
    body = r.json()
    assert body["updated_count"] == 0
    assert body["failed_count"] == 1
    detail = body["failed"][0]["detail"].lower()
    assert "copyright" in detail or "urheberrecht" in detail


def test_bulk_patch_promote_to_club_with_copyright_in_updated(client: TestClient) -> None:
    """Bulk-Promotion MIT copyright_notice -> Asset in updated-Liste."""
    app.dependency_overrides[get_tenant_context] = lambda: _SUPERADMIN_TENANT
    mock_cur = MagicMock()
    mock_cur.fetchone.return_value = _PRIVATE_ASSET
    mock_conn = MagicMock()
    mock_cm = MagicMock()
    mock_cm.__enter__.return_value = mock_conn
    mock_cm.__exit__.return_value = False

    with ExitStack() as stack:
        stack.enter_context(patch("routers.media_assets.get_db", return_value=mock_cm))
        stack.enter_context(patch("routers.media_assets.get_cursor", return_value=mock_cur))
        _enter_permission_patches(stack)
        r = client.post(
            "/api/media-assets/bulk-patch",
            json={
                "media_asset_ids": [42],
                "visibility": "club",
                "club_id": 7,
                "copyright_notice": "Verein 2026",
            },
            headers={"X-Auth-Token": "t"},
        )

    assert r.status_code == 200
    body = r.json()
    assert 42 in body["updated"]
    assert body["updated_count"] == 1
