"""Unit tests for tribute photo-URL resolution and ORM field mapping.

The multipart submission endpoint stores a bare S3 object key in
``Tribute.photo_url``. ``TributeResponse`` must resolve that to the
``/api/public/images/{key}`` proxy path, otherwise the admin moderation UI
renders a relative src that 404s and the moderator approves a tribute without
ever seeing the attached photo.

Also covers ``relationship``: the wire field is ``relationship`` but the ORM
attribute is ``relationship_type``, which used to serialise as null always.
"""
import datetime as dt
from types import SimpleNamespace
from uuid import uuid4

from src.apps.memorials.schemas.responses import TributeResponse, resolve_image_url


def _orm_tribute(**overrides):
    """A duck-typed stand-in for a Tribute ORM row (no DB needed)."""
    base = dict(
        id=uuid4(),
        memorial_id=uuid4(),
        submitter_name="Mary Doyle",
        submitter_email="mary@example.com",
        relationship_type="Former student",
        message="She taught me to read.",
        photo_url="tributes/tenant-1/abc123.jpg",
        status="pending",
        submitted_at=dt.datetime(2026, 8, 20, 10, 0, tzinfo=dt.timezone.utc),
        moderated_at=None,
    )
    base.update(overrides)
    return SimpleNamespace(**base)


class TestResolveImageUrl:
    def test_bare_s3_key_becomes_a_proxy_path(self):
        assert resolve_image_url("tributes/t1/a.jpg") == "/api/public/images/tributes/t1/a.jpg"

    def test_already_proxied_path_is_idempotent(self):
        assert resolve_image_url("/api/public/images/a.jpg") == "/api/public/images/a.jpg"

    def test_absolute_urls_and_odd_schemes_are_dropped(self):
        # A legacy row could hold a client-supplied URL. Emitting it would put an
        # attacker-controlled host into an <img src> shown to every moderator.
        assert resolve_image_url("https://evil.example.com/px.gif") is None
        assert resolve_image_url("http://evil.example.com/px.gif") is None
        assert resolve_image_url("//evil.example.com/px.gif") is None
        assert resolve_image_url("javascript:alert(1)") is None
        assert resolve_image_url("data:text/html,<script>") is None

    def test_traversal_out_of_the_key_space_is_dropped(self):
        assert resolve_image_url("../../etc/passwd") is None

    def test_qr_keys_with_spaces_and_parens_are_percent_encoded(self):
        # QR object keys embed a reference_id (plot_ref / section code) whose
        # own validation pattern allows spaces and parentheses. Rejecting them
        # made the QR silently vanish from the public memorial page.
        assert (
            resolve_image_url("tenant/a/qr/plot/B 204.svg")
            == "/api/public/images/tenant/a/qr/plot/B%20204.svg"
        )
        assert (
            resolve_image_url("tenant/a/qr/plot/Sec A (North).svg")
            == "/api/public/images/tenant/a/qr/plot/Sec%20A%20%28North%29.svg"
        )

    def test_encoding_is_per_segment_so_the_key_path_survives(self):
        # "/" separates segments and must NOT be encoded, or the proxy's
        # {key:path} param would no longer reconstruct the original S3 key.
        assert (
            resolve_image_url("tenant/a/qr/headstone/B-1.svg")
            == "/api/public/images/tenant/a/qr/headstone/B-1.svg"
        )

    def test_empty_values_return_none(self):
        assert resolve_image_url(None) is None
        assert resolve_image_url("") is None


class TestTributeResponse:
    def test_photo_key_is_resolved_to_the_image_proxy(self):
        model = TributeResponse.model_validate(_orm_tribute())
        assert model.photo_url == "/api/public/images/tributes/tenant-1/abc123.jpg"

    def test_missing_photo_stays_none(self):
        model = TributeResponse.model_validate(_orm_tribute(photo_url=None))
        assert model.photo_url is None

    def test_absolute_legacy_photo_url_is_dropped_not_rendered(self):
        model = TributeResponse.model_validate(
            _orm_tribute(photo_url="https://legacy.example.com/a.jpg")
        )
        assert model.photo_url is None

    def test_relationship_is_populated_from_relationship_type(self):
        model = TributeResponse.model_validate(_orm_tribute())
        assert model.relationship == "Former student"

    def test_remaining_fields_survive_the_validator(self):
        orm = _orm_tribute()
        model = TributeResponse.model_validate(orm)
        assert model.id == orm.id
        assert model.memorial_id == orm.memorial_id
        assert model.submitter_name == "Mary Doyle"
        assert model.submitter_email == "mary@example.com"
        assert model.message == "She taught me to read."
        assert model.status == "pending"
        assert model.submitted_at == orm.submitted_at
        assert model.moderated_at is None

    def test_dict_input_is_also_resolved(self):
        # The router dumps to a dict and re-adds deceased_name; validating a
        # dict must not skip resolution or lose the field.
        model = TributeResponse.model_validate(
            {
                "id": uuid4(),
                "memorial_id": uuid4(),
                "submitter_name": "Mary",
                "message": "hi",
                "photo_url": "tributes/t1/a.jpg",
                "status": "approved",
                "submitted_at": dt.datetime(2026, 8, 20, tzinfo=dt.timezone.utc),
                "deceased_name": "Eileen Fitzgerald",
            }
        )
        assert model.photo_url == "/api/public/images/tributes/t1/a.jpg"
        assert model.deceased_name == "Eileen Fitzgerald"
