"""W20E5 — fal_angle_helpers image_call_budget wiring (RED).

``apply_fal_angle`` issues a POST to fal.run (single generation request)
followed by a GET that downloads the produced image. Only the generation
call (``fal.run``) is counted against the cap; the result-URL download is
not.
"""
from __future__ import annotations

from unittest.mock import MagicMock

import pytest

from app.core.image_call_budget import (
    ImageCallBudget,
    ImageCallBudgetExceeded,
    install_budget,
    uninstall_budget,
)


@pytest.fixture(autouse=True)
def _isolate_budget():
    uninstall_budget()
    yield
    uninstall_budget()


def _install_fake_settings(monkeypatch):
    """Provide a non-empty fal_key so the Request build doesn't blow up."""
    import app.services.fal_angle_helpers as fal
    monkeypatch.setattr(fal.settings, "fal_key", "dummy-fal-key", raising=False)


def _install_fake_tracer(monkeypatch):
    fake_tracer = MagicMock()
    fake_tracer.log = MagicMock()
    monkeypatch.setattr(
        "app.modules.llm.image_tracer.get_image_tracer",
        lambda: fake_tracer,
    )


class _GenResponse:
    """Fake fal.run JSON response with one image URL."""
    def __enter__(self): return self
    def __exit__(self, *a): return False
    def read(self):
        import json
        return json.dumps({
            "images": [{"url": "https://fake.example/result.png"}],
        }).encode()


class _DLResponse:
    """Fake download response — returns raw bytes."""
    def __enter__(self): return self
    def __exit__(self, *a): return False
    def read(self):
        return b"PNGDATA" * 64


def test_no_budget_keeps_legacy_call_path(monkeypatch):
    _install_fake_settings(monkeypatch)
    _install_fake_tracer(monkeypatch)
    import app.services.fal_angle_helpers as fal

    calls = {"gen": 0, "dl": 0}

    def _fake_urlopen(req_or_url, timeout=None):
        # First call uses Request object (fal.run); second uses URL string.
        if hasattr(req_or_url, "full_url") or hasattr(req_or_url, "get_full_url"):
            calls["gen"] += 1
            return _GenResponse()
        # Otherwise it's a download URL string.
        calls["dl"] += 1
        return _DLResponse()

    monkeypatch.setattr(fal.urllib.request, "urlopen", _fake_urlopen)

    out, elapsed = fal.apply_fal_angle(
        img_bytes=b"X" * 1024, horizontal=30, vertical=0, zoom=5,
    )
    assert out is not None
    assert calls["gen"] == 1
    assert calls["dl"] == 1


def test_cap_zero_blocks_fal_run_before_network(monkeypatch):
    _install_fake_settings(monkeypatch)
    _install_fake_tracer(monkeypatch)
    import app.services.fal_angle_helpers as fal

    fake_urlopen = MagicMock()
    monkeypatch.setattr(fal.urllib.request, "urlopen", fake_urlopen)

    install_budget(ImageCallBudget(cap=0))
    with pytest.raises(ImageCallBudgetExceeded):
        fal.apply_fal_angle(
            img_bytes=b"X" * 1024, horizontal=30, vertical=0, zoom=5,
        )
    assert fake_urlopen.call_count == 0


def test_cap_one_counts_only_fal_run_not_download(monkeypatch):
    """The result-URL download must NOT count against the cap."""
    _install_fake_settings(monkeypatch)
    _install_fake_tracer(monkeypatch)
    import app.services.fal_angle_helpers as fal

    calls = {"gen": 0, "dl": 0}

    def _fake_urlopen(req_or_url, timeout=None):
        if hasattr(req_or_url, "full_url") or hasattr(req_or_url, "get_full_url"):
            calls["gen"] += 1
            return _GenResponse()
        calls["dl"] += 1
        return _DLResponse()

    monkeypatch.setattr(fal.urllib.request, "urlopen", _fake_urlopen)

    budget = ImageCallBudget(cap=1)
    install_budget(budget)
    out, _elapsed = fal.apply_fal_angle(
        img_bytes=b"X" * 1024, horizontal=30, vertical=0, zoom=5,
    )
    assert out is not None
    assert calls["gen"] == 1
    assert calls["dl"] == 1
    # cap used by gen only — download did not increment.
    assert budget.snapshot() == {"cap": 1, "used": 1, "denied": 0, "remaining": 0}


def test_cap_exceeded_not_swallowed_by_broad_except(monkeypatch):
    """The outer broad ``except Exception`` must not turn budget errors
    into a silent ``(None, 0)`` return."""
    _install_fake_settings(monkeypatch)
    _install_fake_tracer(monkeypatch)
    import app.services.fal_angle_helpers as fal

    fake_urlopen = MagicMock()
    monkeypatch.setattr(fal.urllib.request, "urlopen", fake_urlopen)

    install_budget(ImageCallBudget(cap=0))
    with pytest.raises(ImageCallBudgetExceeded):
        fal.apply_fal_angle(
            img_bytes=b"X" * 1024, horizontal=30, vertical=0, zoom=5,
        )
