import asyncio
import threading
from pathlib import Path

import pytest

import gateway.platforms.base as base


@pytest.mark.asyncio
@pytest.mark.parametrize(
    ("async_name", "sync_name", "args"),
    [
        ("cache_image_from_bytes_async", "cache_image_from_bytes", (b"data", ".png")),
        ("cache_audio_from_bytes_async", "cache_audio_from_bytes", (b"data", ".ogg")),
        ("cache_video_from_bytes_async", "cache_video_from_bytes", (b"data", ".mp4")),
        (
            "cache_document_from_bytes_async",
            "cache_document_from_bytes",
            (b"data", "report.pdf"),
        ),
    ],
)
async def test_async_cache_wrappers_keep_event_loop_responsive(
    monkeypatch, async_name, sync_name, args
):
    loop_thread = threading.get_ident()
    cache_started = threading.Event()
    release_cache = threading.Event()
    observed = {}

    def blocking_cache(*call_args):
        observed["thread"] = threading.get_ident()
        observed["args"] = call_args
        cache_started.set()
        observed["ticker_ran_during_cache"] = release_cache.wait(timeout=1)
        return "cached"

    monkeypatch.setattr(base, sync_name, blocking_cache)

    async def ticker():
        while not cache_started.is_set():
            await asyncio.sleep(0)
        release_cache.set()

    ticker_task = asyncio.create_task(ticker())
    result = await getattr(base, async_name)(*args)
    await ticker_task

    assert result == "cached"
    assert observed["args"] == args
    assert observed["thread"] != loop_thread
    assert observed["ticker_ran_during_cache"] is True


@pytest.mark.asyncio
async def test_async_cache_wrapper_propagates_validation_errors(monkeypatch):
    def reject_image(data, ext):
        raise ValueError("invalid image")

    monkeypatch.setattr(base, "cache_image_from_bytes", reject_image)

    with pytest.raises(ValueError, match="invalid image"):
        await base.cache_image_from_bytes_async(b"not-an-image", ".png")


@pytest.mark.asyncio
async def test_cache_media_bytes_async_runs_off_loop_and_forwards_kwargs(monkeypatch):
    loop_thread = threading.get_ident()
    observed = {}

    def fake_cache_media_bytes(data, *, filename="", mime_type="", default_kind=None):
        observed["thread"] = threading.get_ident()
        observed["call"] = (data, filename, mime_type, default_kind)
        return "cached-media"

    monkeypatch.setattr(base, "cache_media_bytes", fake_cache_media_bytes)

    result = await base.cache_media_bytes_async(
        b"payload", filename="report.pdf", mime_type="application/pdf", default_kind="document"
    )

    assert result == "cached-media"
    assert observed["call"] == (b"payload", "report.pdf", "application/pdf", "document")
    assert observed["thread"] != loop_thread


@pytest.mark.asyncio
async def test_async_cache_wrapper_uses_active_profile_home(monkeypatch, tmp_path):
    profile_home = tmp_path / "profile"
    monkeypatch.setenv("HERMES_HOME", str(profile_home))

    cached = await base.cache_image_from_bytes_async(
        b"\x89PNG\r\n\x1a\nminimal", ".png"
    )

    cached_path = Path(cached)
    assert cached_path.parent == profile_home / "cache" / "images"
    assert cached_path.read_bytes() == b"\x89PNG\r\n\x1a\nminimal"
