"""
Module: test_sandbox_errors
Description: Tests for structured error boundaries in the sandbox proxy, REPL client, and formatter.

Implements:
    - docs/guides/developer-environment.md#error-handling-in-dev-mode
    - docs/sdk/error-handling.md#error-response-format
"""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock, PropertyMock

import pytest

from huitzo_cli.errors import SandboxCommandError, ServerError
from huitzo_cli.repl.formatter import OutputFormatter


def _mock_response(status_code: int, json_data: dict | None = None, *, json_error: bool = False):
    """Create a mock httpx response with is_success property."""
    resp = MagicMock()
    resp.status_code = status_code
    type(resp).is_success = PropertyMock(return_value=(200 <= status_code < 300))
    if json_error:
        resp.json.side_effect = ValueError("not json")
        resp.text = "Internal Server Error"
    elif json_data is not None:
        resp.json.return_value = json_data
    return resp


class TestSandboxCommandError:
    """Test SandboxCommandError exception class."""

    def test_attributes(self) -> None:
        err = SandboxCommandError(
            error_type="StorageError",
            message="ctx.files is not available in sandbox mode.",
            command="save-visit",
            code="STORAGE_ERROR",
        )
        assert err.error_type == "StorageError"
        assert err.command == "save-visit"
        assert err.code == "STORAGE_ERROR"
        assert str(err) == "ctx.files is not available in sandbox mode."
        assert err.user_message == "ctx.files is not available in sandbox mode."

    def test_optional_code(self) -> None:
        err = SandboxCommandError(
            error_type="InternalError",
            message="Command failed unexpectedly.",
            command="boom",
        )
        assert err.code is None


class TestFormatterCommandError:
    """Test OutputFormatter.format_command_error()."""

    def test_format_command_error_output(self) -> None:
        console = MagicMock()
        formatter = OutputFormatter(console)
        formatter.format_command_error("StorageError", "ctx.files is not available", "save-visit")
        console.print.assert_called_once()
        output = console.print.call_args[0][0]
        assert "StorageError" in output
        assert "save-visit" in output
        assert "ctx.files is not available" in output


class TestREPLClientStructuredErrors:
    """Test that REPLClient parses structured error responses."""

    @pytest.mark.anyio
    async def test_huitzo_error_raises_sandbox_command_error(self) -> None:
        from huitzo_cli.repl.client import REPLClient

        client = REPLClient("http://localhost:9999")

        mock_http = AsyncMock()
        mock_http.post.return_value = _mock_response(
            500,
            {
                "error": "StorageError",
                "message": "ctx.files is not available in sandbox mode.",
                "code": "STORAGE_ERROR",
                "command": "save-visit",
            },
        )
        client._client = mock_http

        with pytest.raises(SandboxCommandError) as exc_info:
            await client.execute_command("save-visit", {})

        err = exc_info.value
        assert err.error_type == "StorageError"
        assert err.command == "save-visit"
        assert err.code == "STORAGE_ERROR"
        assert "ctx.files" in str(err)

    @pytest.mark.anyio
    async def test_internal_error_raises_sandbox_command_error(self) -> None:
        from huitzo_cli.repl.client import REPLClient

        client = REPLClient("http://localhost:9999")

        mock_http = AsyncMock()
        mock_http.post.return_value = _mock_response(
            500,
            {
                "error": "InternalError",
                "message": "Command failed unexpectedly. Check server logs with --log-level=debug for details.",
                "command": "boom",
            },
        )
        client._client = mock_http

        with pytest.raises(SandboxCommandError) as exc_info:
            await client.execute_command("boom", {})

        err = exc_info.value
        assert err.error_type == "InternalError"
        assert err.command == "boom"

    @pytest.mark.anyio
    async def test_non_json_error_falls_through_to_typed_error(self) -> None:
        from huitzo_cli.repl.client import REPLClient

        client = REPLClient("http://localhost:9999")

        mock_http = AsyncMock()
        mock_http.post.return_value = _mock_response(500, json_error=True)
        client._client = mock_http

        with pytest.raises(ServerError):
            await client.execute_command("boom", {})

    @pytest.mark.anyio
    async def test_success_response_returns_data(self) -> None:
        from huitzo_cli.repl.client import REPLClient

        client = REPLClient("http://localhost:9999")

        mock_http = AsyncMock()
        mock_http.post.return_value = _mock_response(200, {"data": {"result": "ok"}})
        client._client = mock_http

        result = await client.execute_command("echo", {"message": "hi"})
        assert result == {"data": {"result": "ok"}}
