"""Invariants for the exec-approval template method on BasePlatformAdapter.

The choice set (once / session / always / deny, minus the persistent tiers under a smart deny)
used to be re-derived in every button adapter and needed three separate "same fix × N adapters"
sweeps. It now comes from one place; adapters only render it.
"""

from typing import Any, Dict

import pytest

from gateway.config import Platform, PlatformConfig
from gateway.platforms.base import BasePlatformAdapter, ExecApprovalPrompt, SendResult
from gateway.run_turn_runner import _renders_exec_approval_buttons


class _Plain(BasePlatformAdapter):
    def __init__(self):
        super().__init__(PlatformConfig(enabled=True), Platform.TELEGRAM)

    async def connect(self, *, is_reconnect: bool = False) -> bool:
        return True

    async def disconnect(self) -> None:
        pass

    async def send(self, *a: Any, **k: Any) -> SendResult:
        return SendResult(success=True)

    async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
        return {}


class _Buttons(_Plain):
    def __init__(self):
        super().__init__()
        self.prompts: list = []

    async def _send_exec_approval_prompt(self, prompt: ExecApprovalPrompt) -> SendResult:
        self.prompts.append(prompt)
        return SendResult(success=True, message_id="m1")


@pytest.mark.parametrize(
    "allow_permanent, allow_session, smart_denied, expected",
    [
        (True, True, False, ["once", "session", "always", "deny"]),
        (False, True, False, ["once", "session", "deny"]),
        (True, False, False, ["once", "deny"]),          # no session tier → no permanent tier either
        (True, True, True, ["once", "deny"]),            # smart deny: owner override is one-shot
        (False, False, True, ["once", "deny"]),
    ],
)
@pytest.mark.asyncio
async def test_choice_set_follows_the_shared_rule(allow_permanent, allow_session, smart_denied, expected):
    adapter = _Buttons()
    result = await adapter.send_exec_approval(
        "chat", "rm -rf /tmp/x", "sess", description="cleanup",
        allow_permanent=allow_permanent, allow_session=allow_session, smart_denied=smart_denied)
    assert result.success
    (prompt,) = adapter.prompts
    assert prompt.choices == expected
    assert "rm -rf /tmp/x" in prompt.text and "cleanup" in prompt.text


@pytest.mark.asyncio
async def test_command_is_truncated_to_the_platform_budget():
    class _Small(_Buttons):
        _EA_CMD_BUDGET = 20

    adapter = _Small()
    await adapter.send_exec_approval("chat", "x" * 100, "sess")
    (prompt,) = adapter.prompts
    assert "x" * 20 + "..." in prompt.text and "x" * 21 not in prompt.text
    assert prompt.command == "x" * 100  # the raw command stays available for embeds/cards


def test_runner_only_offers_buttons_to_adapters_that_render_them():
    """Plain adapters get the text ``/approve`` prompt; the base send_exec_approval must not make
    the runner believe every adapter has buttons (its default reports failure and the runner would
    log a spurious fallback on every approval)."""
    assert _renders_exec_approval_buttons(_Buttons) is True
    assert _renders_exec_approval_buttons(_Plain) is False

    class _DuckTyped:
        async def send_exec_approval(self, *a, **k):
            return SendResult(success=True)

    assert _renders_exec_approval_buttons(_DuckTyped) is True
    assert _renders_exec_approval_buttons(object) is False
