"""
P-13: Content-Melde-Backend – Backend-Tests.

Abgedeckt (15 Faelle):
  1.  submit_report ohne good_faith_confirmed=False → 400
  2.  submit_report mit leerem report_description → 422
  3.  submit_report mit fehlender reporter_email → 422
  4.  submit_report ohne Login fuer official-Medium – Erfolgspfad
  5.  submit_report ohne Login fuer nicht-official-Medium → 404
  6.  submit_report mit Login fuer sichtbares club-Medium – Erfolgspfad
  7.  submit_report mit Login fuer nicht-sichtbares fremdes Medium → 404
  8.  Prioritaet high fuer illegal_content/minors/youth_protection
  9.  Prioritaet normal fuer copyright/other
 10.  list_inbox als Nicht-Admin → 403
 11.  list_inbox als Plattform-Admin – gibt Liste zurueck
 12.  patch_report status 'under_review' – Erfolgspfad
 13.  patch_report 'resolved_no_action' ohne resolution_note → 400
 14.  set_legal_hold_from_report als Nicht-Superadmin → 403
 15.  set_legal_hold_from_report als Superadmin – Erfolgspfad (reason_code-Mapping)
"""
from __future__ import annotations

import os
from unittest.mock import MagicMock, patch

import pytest
from fastapi import HTTPException

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

from routers.content_reports import (
    HIGH_PRIORITY_REASONS,
    _REASON_TO_HOLD_CODE,
    _is_media_asset_visible_anonymous,
    _is_media_asset_visible_to_profile,
    REPORT_REASONS,
    ContentReportCreate,
    ContentReportPatch,
)


# ---------------------------------------------------------------------------
# Hilfsfunktionen
# ---------------------------------------------------------------------------

def _make_cur(fetchone_val=None):
    cur = MagicMock()
    cur.fetchone.return_value = fetchone_val
    cur.fetchall.return_value = []
    return cur


def _row(data: dict):
    """Simuliert eine psycopg2-DictRow."""
    m = MagicMock()
    m.__getitem__ = lambda self, k: data[k]
    m.keys = lambda: data.keys()
    return m


# ---------------------------------------------------------------------------
# 1. good_faith_confirmed=False → 400
# ---------------------------------------------------------------------------

def test_submit_requires_good_faith():
    body = ContentReportCreate(
        target_type="media_asset",
        target_id=1,
        report_reason="copyright",
        report_description="A" * 20,
        reporter_name="Max",
        reporter_email="max@example.com",
        good_faith_confirmed=False,
    )
    from routers.content_reports import submit_content_report
    with pytest.raises(HTTPException) as exc:
        submit_content_report(body)
    assert exc.value.status_code == 400
    assert "Gutglauben" in exc.value.detail


# ---------------------------------------------------------------------------
# 2. Leere report_description → 422 (Pydantic)
# ---------------------------------------------------------------------------

def test_submit_requires_description():
    with pytest.raises(Exception):
        ContentReportCreate(
            target_type="media_asset",
            target_id=1,
            report_reason="copyright",
            report_description="ab",  # unter min_length=10
            reporter_name="Max",
            reporter_email="max@example.com",
            good_faith_confirmed=True,
        )


# ---------------------------------------------------------------------------
# 3. Ungueltige E-Mail → 422 (Pydantic Validator)
# ---------------------------------------------------------------------------

def test_submit_requires_valid_email():
    with pytest.raises(Exception):
        ContentReportCreate(
            target_type="media_asset",
            target_id=1,
            report_reason="copyright",
            report_description="A" * 20,
            reporter_name="Max",
            reporter_email="kein-at-zeichen",
            good_faith_confirmed=True,
        )


# ---------------------------------------------------------------------------
# 4. Anonym – official-Medium → sichtbar
# ---------------------------------------------------------------------------

def test_anonymous_official_medium_visible():
    cur = _make_cur(fetchone_val={"1": 1})
    cur.fetchone.return_value = MagicMock()
    result = _is_media_asset_visible_anonymous(cur, asset_id=42)
    assert result is True


# ---------------------------------------------------------------------------
# 5. Anonym – nicht-official-Medium → nicht sichtbar
# ---------------------------------------------------------------------------

def test_anonymous_non_official_medium_not_visible():
    cur = _make_cur(fetchone_val=None)
    result = _is_media_asset_visible_anonymous(cur, asset_id=99)
    assert result is False


# ---------------------------------------------------------------------------
# 6. Eingeloggter Nutzer – sichtbares club-Medium → True
# ---------------------------------------------------------------------------

def test_logged_in_club_member_can_see_club_media():
    asset_row = MagicMock()
    asset_row.keys = lambda: ["id", "visibility", "club_id", "uploaded_by_profile_id"]
    asset_row.__getitem__ = lambda s, k: {
        "id": 7,
        "visibility": "club",
        "club_id": 3,
        "uploaded_by_profile_id": 99,
    }[k]

    member_row = MagicMock()  # Mitgliedschaft gefunden

    cur = MagicMock()
    cur.fetchone.side_effect = [asset_row, member_row]

    result = _is_media_asset_visible_to_profile(cur, asset_id=7, profile_id=1, global_role="trainer")
    assert result is True


# ---------------------------------------------------------------------------
# 7. Eingeloggter Nutzer – fremdes Medium → False
# ---------------------------------------------------------------------------

def test_logged_in_non_member_cannot_see_club_media():
    asset_row = MagicMock()
    asset_row.keys = lambda: ["id", "visibility", "club_id", "uploaded_by_profile_id"]
    asset_row.__getitem__ = lambda s, k: {
        "id": 7,
        "visibility": "club",
        "club_id": 3,
        "uploaded_by_profile_id": 99,
    }[k]

    cur = MagicMock()
    # Erster fetchone: Asset-Zeile, zweiter: kein Mitglied
    cur.fetchone.side_effect = [asset_row, None]

    result = _is_media_asset_visible_to_profile(cur, asset_id=7, profile_id=5, global_role="trainer")
    assert result is False


# ---------------------------------------------------------------------------
# 8. Prioritaet high fuer kritische Gruende
# ---------------------------------------------------------------------------

def test_high_priority_reasons():
    assert "illegal_content" in HIGH_PRIORITY_REASONS
    assert "minors" in HIGH_PRIORITY_REASONS
    assert "youth_protection" in HIGH_PRIORITY_REASONS


# ---------------------------------------------------------------------------
# 9. Prioritaet normal fuer andere Gruende
# ---------------------------------------------------------------------------

def test_normal_priority_reasons():
    normal_reasons = REPORT_REASONS - HIGH_PRIORITY_REASONS
    assert "copyright" in normal_reasons
    assert "other" in normal_reasons
    assert "privacy" in normal_reasons


# ---------------------------------------------------------------------------
# 10. list_inbox als Nicht-Admin → 403
# ---------------------------------------------------------------------------

def test_list_inbox_requires_platform_admin():
    from routers.content_reports import list_inbox_content_reports
    tenant = MagicMock()
    tenant.global_role = "trainer"
    tenant.profile_id = 99

    # COUNT-Abfrage fuer Club-Admin-Rollen → 0 → 403
    cnt_row = _row({"cnt": 0})
    mock_cur = MagicMock()
    mock_cur.fetchone.return_value = cnt_row

    mock_conn = MagicMock()
    mock_conn_ctx = MagicMock()
    mock_conn_ctx.__enter__ = MagicMock(return_value=mock_conn)
    mock_conn_ctx.__exit__ = MagicMock(return_value=False)

    with patch("routers.content_reports.get_db", return_value=mock_conn_ctx), \
         patch("routers.content_reports.get_cursor", return_value=mock_cur):
        with pytest.raises(HTTPException) as exc:
            list_inbox_content_reports(tenant=tenant)
    assert exc.value.status_code == 403


# ---------------------------------------------------------------------------
# 11. list_inbox als Plattform-Admin – gibt Liste zurueck
# ---------------------------------------------------------------------------

def test_list_inbox_as_admin_returns_list():
    from routers.content_reports import list_inbox_content_reports

    tenant = MagicMock()
    tenant.global_role = "admin"

    mock_conn = MagicMock()
    mock_cur = MagicMock()
    mock_cur.fetchall.return_value = []
    mock_conn.__enter__ = MagicMock(return_value=mock_conn)
    mock_conn.__exit__ = MagicMock(return_value=False)
    mock_conn_ctx = MagicMock()
    mock_conn_ctx.__enter__ = MagicMock(return_value=mock_conn)
    mock_conn_ctx.__exit__ = MagicMock(return_value=False)

    with patch("routers.content_reports.get_db", return_value=mock_conn_ctx), \
         patch("routers.content_reports.get_cursor", return_value=mock_cur):
        result = list_inbox_content_reports(tenant=tenant)
    assert isinstance(result, list)


# ---------------------------------------------------------------------------
# 12. patch_report status 'under_review' – Erfolgspfad
# ---------------------------------------------------------------------------

def test_patch_report_under_review():
    from routers.content_reports import patch_content_report

    tenant = MagicMock()
    tenant.global_role = "admin"
    tenant.profile_id = 1

    body = ContentReportPatch(status="under_review")

    existing_row = MagicMock()
    existing_row.__getitem__ = lambda s, k: {
        "id": 5, "status": "submitted",
        "target_type": "media_asset", "target_id": 42, "resolution_note": None,
    }[k]
    existing_row.keys = lambda: ["id", "status", "target_type", "target_id", "resolution_note"]

    updated_row = MagicMock()
    updated_row.__getitem__ = lambda s, k: {"id": 5, "status": "under_review"}[k]
    updated_row.keys = lambda: ["id", "status"]

    mock_cur = MagicMock()
    mock_cur.fetchone.side_effect = [existing_row, updated_row]

    mock_conn = MagicMock()
    mock_conn_ctx = MagicMock()
    mock_conn_ctx.__enter__ = MagicMock(return_value=mock_conn)
    mock_conn_ctx.__exit__ = MagicMock(return_value=False)

    with patch("routers.content_reports.get_db", return_value=mock_conn_ctx), \
         patch("routers.content_reports.get_cursor", return_value=mock_cur):
        result = patch_content_report(5, body, tenant=tenant)

    assert result["ok"] is True
    assert result["status"] == "under_review"


# ---------------------------------------------------------------------------
# 13. patch_report 'resolved_no_action' ohne resolution_note → 400
# ---------------------------------------------------------------------------

def test_patch_resolved_no_action_requires_note():
    from routers.content_reports import patch_content_report

    tenant = MagicMock()
    tenant.global_role = "admin"
    tenant.profile_id = 1

    body = ContentReportPatch(status="resolved_no_action", resolution_note=None)

    with pytest.raises(HTTPException) as exc:
        patch_content_report(99, body, tenant=tenant)
    assert exc.value.status_code == 400
    assert "Begründung" in exc.value.detail


# ---------------------------------------------------------------------------
# 14. set_legal_hold_from_report als Nicht-Superadmin → 403
# ---------------------------------------------------------------------------

def test_legal_hold_from_report_requires_superadmin():
    from routers.content_reports import set_legal_hold_from_report, ContentReportLegalHoldBody

    tenant = MagicMock()
    tenant.global_role = "admin"  # nur admin, nicht superadmin

    body = ContentReportLegalHoldBody(reason_note="Test-Begruendung")

    with pytest.raises(HTTPException) as exc:
        set_legal_hold_from_report(1, body, tenant=tenant)
    assert exc.value.status_code == 403


# ---------------------------------------------------------------------------
# 15. Reason-Code-Mapping: copyright → copyright_complaint
# ---------------------------------------------------------------------------

def test_reason_code_mapping():
    assert _REASON_TO_HOLD_CODE["copyright"] == "copyright_complaint"
    assert _REASON_TO_HOLD_CODE["minors"] == "youth_protection"
    assert _REASON_TO_HOLD_CODE["illegal_content"] == "illegal_content"
    assert _REASON_TO_HOLD_CODE["privacy"] == "privacy_complaint"
    assert _REASON_TO_HOLD_CODE["image_rights"] == "rights_dispute"
    assert _REASON_TO_HOLD_CODE["other"] == "other"
    # Alle Reason-Codes muessen gemappt sein
    for reason in REPORT_REASONS:
        assert reason in _REASON_TO_HOLD_CODE, f"{reason} fehlt im Mapping"
