"""Outbound-media tests for PhotonAdapter.

Photon ships outbound attachments via spectrum-ts' ``attachment()`` /
``voice()`` content builders, reached through the Node sidecar's
``/send-attachment`` endpoint. These tests stub ``_sidecar_call`` so we
can assert the endpoint + body shape each ``send_*`` override produces
without spawning Node or binding ports.
"""
from __future__ import annotations

import os
from typing import Any, Dict, List, Tuple

import pytest

from gateway.config import PlatformConfig
from plugins.platforms.photon import adapter as photon_adapter
from plugins.platforms.photon.adapter import PhotonAdapter


def _make_adapter(monkeypatch: pytest.MonkeyPatch) -> PhotonAdapter:
    monkeypatch.setenv("PHOTON_PROJECT_ID", "test-project-id")
    monkeypatch.setenv("PHOTON_PROJECT_SECRET", "test-project-secret")
    monkeypatch.delenv("PHOTON_WEBHOOK_SECRET", raising=False)
    cfg = PlatformConfig(enabled=True, token="", extra={})
    return PhotonAdapter(cfg)


def _capture_sidecar(adapter: PhotonAdapter) -> List[Tuple[str, Dict[str, Any]]]:
    """Replace ``_sidecar_call`` with a recorder that returns a fixed id."""
    calls: List[Tuple[str, Dict[str, Any]]] = []

    async def _fake_call(path: str, body: Dict[str, Any]) -> Dict[str, Any]:
        calls.append((path, body))
        return {"ok": True, "messageId": "msg-123"}

    adapter._sidecar_call = _fake_call  # type: ignore[assignment]
    return calls


@pytest.fixture()
def real_file(tmp_path) -> str:
    p = tmp_path / "photo.jpg"
    p.write_bytes(b"\xff\xd8\xff\xe0fake-jpeg")
    return str(p)


def _patch_safe_path(monkeypatch: pytest.MonkeyPatch) -> None:
    """Make path validation a passthrough so tmp files outside the cache pass."""
    monkeypatch.setattr(
        PhotonAdapter,
        "validate_media_delivery_path",
        staticmethod(lambda p: p if os.path.exists(p) else None),
    )


@pytest.mark.asyncio
async def test_send_image_file_hits_attachment_endpoint(
    monkeypatch: pytest.MonkeyPatch, real_file: str
) -> None:
    _patch_safe_path(monkeypatch)
    adapter = _make_adapter(monkeypatch)
    calls = _capture_sidecar(adapter)

    result = await adapter.send_image_file(
        "any;-;+15551234567", real_file, caption="look"
    )

    assert result.success is True
    assert result.message_id == "msg-123"
    assert len(calls) == 1
    path, body = calls[0]
    assert path == "/send-attachment"
    assert body["spaceId"] == "any;-;+15551234567"
    assert body["path"] == real_file
    assert body["kind"] == "attachment"
    assert body["caption"] == "look"
    assert body["mimeType"] == "image/jpeg"  # inferred from .jpg


@pytest.mark.asyncio
async def test_send_voice_marks_kind_voice(
    monkeypatch: pytest.MonkeyPatch, tmp_path
) -> None:
    _patch_safe_path(monkeypatch)
    audio = tmp_path / "note.m4a"
    audio.write_bytes(b"fake-audio")
    adapter = _make_adapter(monkeypatch)
    calls = _capture_sidecar(adapter)

    result = await adapter.send_voice("any;-;+1", str(audio))

    assert result.success is True
    path, body = calls[0]
    assert path == "/send-attachment"
    assert body["kind"] == "voice"


@pytest.mark.asyncio
async def test_send_document_passes_filename(
    monkeypatch: pytest.MonkeyPatch, tmp_path
) -> None:
    _patch_safe_path(monkeypatch)
    doc = tmp_path / "report.pdf"
    doc.write_bytes(b"%PDF-1.4 fake")
    adapter = _make_adapter(monkeypatch)
    calls = _capture_sidecar(adapter)

    await adapter.send_document("any;-;+1", str(doc), file_name="Q3.pdf")

    _, body = calls[0]
    assert body["kind"] == "attachment"
    assert body["name"] == "Q3.pdf"
    assert body["mimeType"] == "application/pdf"


@pytest.mark.asyncio
async def test_send_video_passes_through(
    monkeypatch: pytest.MonkeyPatch, tmp_path
) -> None:
    _patch_safe_path(monkeypatch)
    vid = tmp_path / "clip.mp4"
    vid.write_bytes(b"fake-mp4")
    adapter = _make_adapter(monkeypatch)
    calls = _capture_sidecar(adapter)

    await adapter.send_video("any;+;groupguid", str(vid), caption="watch")

    _, body = calls[0]
    assert body["kind"] == "attachment"
    assert body["caption"] == "watch"


@pytest.mark.asyncio
async def test_send_image_url_caches_then_sends_attachment(
    monkeypatch: pytest.MonkeyPatch, real_file: str
) -> None:
    _patch_safe_path(monkeypatch)
    adapter = _make_adapter(monkeypatch)
    calls = _capture_sidecar(adapter)

    async def _fake_cache(url: str, *a, **k) -> str:
        assert url == "https://example.com/cat.jpg"
        return real_file

    import gateway.platforms.base as base_mod

    monkeypatch.setattr(base_mod, "cache_image_from_url", _fake_cache)

    result = await adapter.send_image(
        "any;-;+1", "https://example.com/cat.jpg", caption="cat"
    )

    assert result.success is True
    path, body = calls[0]
    assert path == "/send-attachment"
    assert body["path"] == real_file
    assert body["caption"] == "cat"


@pytest.mark.asyncio
async def test_send_image_url_fetch_failure_falls_back_to_text(
    monkeypatch: pytest.MonkeyPatch
) -> None:
    adapter = _make_adapter(monkeypatch)
    calls = _capture_sidecar(adapter)

    async def _boom(url: str, *a, **k) -> str:
        raise RuntimeError("network down")

    import gateway.platforms.base as base_mod

    monkeypatch.setattr(base_mod, "cache_image_from_url", _boom)

    result = await adapter.send_image(
        "any;-;+1", "https://example.com/cat.jpg", caption="cat"
    )

    # Fallback path: base send_image() routes to send() → /send (text).
    assert result.success is True
    assert calls[0][0] == "/send"
    assert "https://example.com/cat.jpg" in calls[0][1]["text"]


@pytest.mark.asyncio
async def test_send_attachment_rejects_unsafe_path(
    monkeypatch: pytest.MonkeyPatch
) -> None:
    # Default validation (no passthrough patch) should reject a nonexistent /
    # traversal path, returning a failed SendResult without calling the sidecar.
    monkeypatch.setattr(
        PhotonAdapter,
        "validate_media_delivery_path",
        staticmethod(lambda p: None),
    )
    adapter = _make_adapter(monkeypatch)
    calls = _capture_sidecar(adapter)

    result = await adapter.send_image_file("any;-;+1", "/etc/passwd")

    assert result.success is False
    assert "unsafe" in (result.error or "")
    assert calls == []  # never reached the sidecar


@pytest.mark.asyncio
async def test_standalone_send_text_then_attachments(
    monkeypatch: pytest.MonkeyPatch, tmp_path
) -> None:
    _patch_safe_path(monkeypatch)
    img = tmp_path / "a.png"
    img.write_bytes(b"\x89PNG fake")
    monkeypatch.setenv("PHOTON_SIDECAR_TOKEN", "tok")

    posted: List[Tuple[str, Dict[str, Any]]] = []

    class _Resp:
        status_code = 200

        @staticmethod
        def json() -> Dict[str, Any]:
            return {"ok": True, "messageId": "m-9"}

    class _FakeClient:
        def __init__(self, *a, **k):
            pass

        async def __aenter__(self):
            return self

        async def __aexit__(self, *a):
            return False

        async def post(self, url: str, json: Dict[str, Any], headers=None):
            posted.append((url, json))
            return _Resp()

    monkeypatch.setattr(photon_adapter.httpx, "AsyncClient", _FakeClient)

    cfg = PlatformConfig(enabled=True, token="", extra={})
    result = await photon_adapter._standalone_send(
        cfg,
        "any;-;+1",
        "hello",
        media_files=[(str(img), False)],
    )

    assert result.get("success") is True
    # First call is the text /send, second is /send-attachment.
    assert posted[0][0].endswith("/send")
    assert posted[0][1]["text"] == "hello"
    assert posted[1][0].endswith("/send-attachment")
    assert posted[1][1]["path"] == str(img)
    assert posted[1][1]["kind"] == "attachment"
    assert posted[1][1]["mimeType"] == "image/png"
