"""Tests for vision_check.py fail-closed behavior (Build A Phase 1)."""
from __future__ import annotations

import pytest
from unittest.mock import patch, MagicMock

from recoil.core import vision_check


class TestValidateImageFailsClosedOnException:
    def test_generic_exception_returns_passed_false(self, tmp_path):
        """Any non-transient exception should return passed=False."""
        img = tmp_path / "fake.png"
        img.write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 100)

        with patch.object(vision_check, "_get_client") as mock_get_client:
            mock_get_client.side_effect = RuntimeError("totally unrelated failure")

            result = vision_check.validate_image(
                image_path=img,
                checks=[{"name": "test", "question": "Q?"}],
            )

        assert result["passed"] is False
        assert result["gate_closed_by"] == "exception"
        assert "totally unrelated failure" in result["error"]

    def test_value_error_returns_passed_false(self, tmp_path):
        """ValueError (e.g. parse failure) is NOT transient — fail fast."""
        img = tmp_path / "fake.png"
        img.write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 100)

        with patch.object(vision_check, "_get_client") as mock_get_client:
            mock_get_client.side_effect = ValueError("malformed response")

            result = vision_check.validate_image(
                image_path=img,
                checks=[{"name": "test", "question": "Q?"}],
            )

        assert result["passed"] is False
        assert result["gate_closed_by"] == "exception"


class TestRetryTransient:
    def test_retries_service_unavailable_then_succeeds(self):
        """Transient ServiceUnavailable is retried; second call succeeds."""
        # Use the class that _TRANSIENT_EXCEPTIONS was built with at import
        # time — not a fresh import from google.api_core.exceptions. If
        # another test module stubs sys.modules["google.api_core.exceptions"]
        # before vision_check is imported, the two class objects diverge and
        # isinstance() returns False even though they have the same name.
        transient_excs = vision_check._TRANSIENT_EXCEPTIONS
        if not transient_excs:
            pytest.skip("google.api_core not installed — no transient exceptions registered")

        # Pick the first exception class in the tuple (ServiceUnavailable or
        # whichever was registered first — either works for retry verification).
        ServiceUnavailableCls = transient_excs[0]

        attempts = {"count": 0}

        def fn():
            attempts["count"] += 1
            if attempts["count"] < 2:
                raise ServiceUnavailableCls("503")
            return "ok"

        result = vision_check._retry_transient(fn, backoff_s=0.01)
        assert result == "ok"
        assert attempts["count"] == 2

    def test_non_transient_propagates_immediately(self):
        """Non-transient exception (ValueError) fails on first attempt."""
        attempts = {"count": 0}

        def fn():
            attempts["count"] += 1
            raise ValueError("not transient")

        with pytest.raises(ValueError):
            vision_check._retry_transient(fn, backoff_s=0.01)
        assert attempts["count"] == 1
