"""
Testy funkcji autenter:
- mcp_agent0.autenter() — ustawia flagę w state
- client.py — gdy autenter=True: pyta o potwierdzenie, Enter=wykonaj, n=pomiń
- client.py — gdy autenter=False: wywołuje tool bez pytania
"""

import sys
import os
import pytest
from unittest.mock import AsyncMock, MagicMock, patch

sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))

import mcp_agent0
from mcp_agent0 import autenter, is_autenter_enabled, state
import client
from tests.test_client import _config_patch, _make_fake_tool


# ---------------------------------------------------------------------------
# Testy mcp_agent0.autenter — jednostkowe
# ---------------------------------------------------------------------------

def setup_function():
    """Resetuj stan przed każdym testem."""
    state["autenter"] = False


def test_autenter_enable_sets_state():
    autenter(True)
    assert state["autenter"] is True


def test_autenter_disable_sets_state():
    state["autenter"] = True
    autenter(False)
    assert state["autenter"] is False


def test_autenter_enable_returns_message():
    result = autenter(True)
    assert "włączony" in result


def test_autenter_disable_returns_message():
    result = autenter(False)
    assert "wyłączony" in result


def test_autenter_toggle():
    autenter(True)
    assert state["autenter"] is True
    autenter(False)
    assert state["autenter"] is False
    autenter(True)
    assert state["autenter"] is True


# ---------------------------------------------------------------------------
# Testy is_autenter_enabled — jednostkowe
# ---------------------------------------------------------------------------

def test_is_autenter_enabled_returns_false_by_default():
    assert is_autenter_enabled() is False


def test_is_autenter_enabled_returns_true_after_enable():
    autenter(True)
    assert is_autenter_enabled() is True


def test_is_autenter_enabled_returns_false_after_disable():
    autenter(True)
    autenter(False)
    assert is_autenter_enabled() is False


def test_is_autenter_enabled_reflects_state_directly():
    state["autenter"] = True
    assert is_autenter_enabled() is True
    state["autenter"] = False
    assert is_autenter_enabled() is False


def test_is_autenter_enabled_returns_bool():
    assert isinstance(is_autenter_enabled(), bool)


# ---------------------------------------------------------------------------
# Lokalne pomocniki dla testów client.py
# ---------------------------------------------------------------------------

def _tool_response(name="echo", input_data=None, tool_id="t1"):
    """Odpowiedź Anthropic z tool_use — bez atrybutu .text żeby hasattr go nie wykrył."""
    block = MagicMock(spec=["type", "name", "input", "id"])
    block.type = "tool_use"
    block.name = name
    block.input = input_data or {}
    block.id = tool_id
    resp = MagicMock()
    resp.content = [block]
    resp.stop_reason = "tool_use"
    return resp


def _text_response(text="ok"):
    block = MagicMock()
    block.text = text
    block.type = "text"
    resp = MagicMock()
    resp.content = [block]
    resp.stop_reason = "end_turn"
    return resp


def _session_with_tool(tool_name="echo"):
    fake_tools = MagicMock()
    fake_tools.tools = [_make_fake_tool(tool_name)]
    session = AsyncMock()
    session.initialize = AsyncMock()
    session.list_tools = AsyncMock(return_value=fake_tools)
    session.call_tool = AsyncMock(
        return_value=MagicMock(content=[MagicMock(text="wynik")])
    )
    session._received_notification = AsyncMock()
    return session


def _patch_session(session):
    cls = MagicMock()
    cls.return_value.__aenter__ = AsyncMock(return_value=session)
    cls.return_value.__aexit__ = AsyncMock(return_value=False)
    return patch("client.ClientSession", cls)


def _patch_http():
    m = MagicMock()
    m.return_value.__aenter__ = AsyncMock(return_value=(AsyncMock(), AsyncMock(), None))
    m.return_value.__aexit__ = AsyncMock(return_value=False)
    return patch("client.streamable_http_client", m)


# ---------------------------------------------------------------------------
# autenter=True — tool wywoływany automatycznie bez pytania
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_autenter_on_tool_called_without_prompt():
    """Gdy autenter=True, call_tool odpala się bez run_in_executor na potwierdzenie."""
    state["autenter"] = True
    session = _session_with_tool("echo")
    anthropic_mock = MagicMock()
    anthropic_mock.messages.create = AsyncMock(
        side_effect=[_tool_response("echo"), _text_response()]
    )

    executor_calls = []

    async def fake_executor(executor, fn):
        executor_calls.append("call")
        if len(executor_calls) == 1:
            return "zrób coś"
        raise EOFError()

    with _config_patch(), \
         patch("client.anthropic.AsyncAnthropic", return_value=anthropic_mock), \
         _patch_http(), \
         _patch_session(session), \
         patch("asyncio.get_event_loop") as mock_loop:

        loop_mock = MagicMock()
        mock_loop.return_value = loop_mock
        loop_mock.run_in_executor = fake_executor

        await client.run()

    # call_tool: raz do rejestracji, raz do echo
    assert session.call_tool.call_count == 2
    assert session.call_tool.call_args_list[-1].args == ("echo", {})
    # executor tylko dla user input (wiadomość + EOF), nie dla potwierdzenia
    assert executor_calls == ["call", "call"]


# ---------------------------------------------------------------------------
# autenter=False, użytkownik naciska Enter — tool wykonany
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_autenter_off_enter_executes_tool():
    """Gdy autenter=False i użytkownik naciska Enter, tool jest wywołany."""
    state["autenter"] = False
    session = _session_with_tool("echo")
    anthropic_mock = MagicMock()
    anthropic_mock.messages.create = AsyncMock(
        side_effect=[_tool_response("echo"), _text_response()]
    )

    executor_calls = []

    async def fake_executor(executor, fn):
        executor_calls.append("call")
        n = len(executor_calls)
        if n == 1:
            return "zrób coś"   # user input
        if n == 2:
            return ""            # Enter = potwierdź
        raise EOFError()

    with _config_patch(), \
         patch("client.anthropic.AsyncAnthropic", return_value=anthropic_mock), \
         _patch_http(), \
         _patch_session(session), \
         patch("asyncio.get_event_loop") as mock_loop:

        loop_mock = MagicMock()
        mock_loop.return_value = loop_mock
        loop_mock.run_in_executor = fake_executor

        await client.run()

    # call_tool: raz do rejestracji, raz do echo
    assert session.call_tool.call_count == 2
    assert session.call_tool.call_args_list[-1].args == ("echo", {})
    assert len(executor_calls) == 3  # user input + potwierdzenie + EOF


# ---------------------------------------------------------------------------
# autenter=False, użytkownik wpisuje "n" — tool pominięty
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_autenter_off_n_skips_tool():
    """Gdy autenter=False i użytkownik wpisuje 'n', tool nie jest wywołany."""
    state["autenter"] = False
    session = _session_with_tool("echo")
    anthropic_mock = MagicMock()
    anthropic_mock.messages.create = AsyncMock(
        side_effect=[_tool_response("echo"), _text_response()]
    )

    executor_calls = []

    async def fake_executor(executor, fn):
        executor_calls.append("call")
        n = len(executor_calls)
        if n == 1:
            return "zrób coś"
        if n == 2:
            return "n"
        raise EOFError()

    with _config_patch(), \
         patch("client.anthropic.AsyncAnthropic", return_value=anthropic_mock), \
         _patch_http(), \
         _patch_session(session), \
         patch("asyncio.get_event_loop") as mock_loop:

        loop_mock = MagicMock()
        mock_loop.return_value = loop_mock
        loop_mock.run_in_executor = fake_executor

        await client.run()

    # call_tool wołany tylko raz — rejestracja, nie echo
    assert session.call_tool.call_count == 1
    assert session.call_tool.call_args_list[0].args[0] == "management__add_mcp_server"


# ---------------------------------------------------------------------------
# autenter=True, "n" z białymi znakami i wielkie litery
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
@pytest.mark.parametrize("answer", ["N", " n ", "N "])
async def test_autenter_off_n_case_insensitive(answer):
    """'N', ' n ', 'N ' — wszystko pomija tool."""
    state["autenter"] = False
    session = _session_with_tool("echo")
    anthropic_mock = MagicMock()
    anthropic_mock.messages.create = AsyncMock(
        side_effect=[_tool_response("echo"), _text_response()]
    )

    call_n = 0

    async def fake_executor(executor, fn):
        nonlocal call_n
        call_n += 1
        if call_n == 1:
            return "zrób coś"
        if call_n == 2:
            return answer
        raise EOFError()

    with _config_patch(), \
         patch("client.anthropic.AsyncAnthropic", return_value=anthropic_mock), \
         _patch_http(), \
         _patch_session(session), \
         patch("asyncio.get_event_loop") as mock_loop:

        loop_mock = MagicMock()
        mock_loop.return_value = loop_mock
        loop_mock.run_in_executor = fake_executor

        await client.run()

    # call_tool wołany tylko raz — rejestracja, nie echo
    assert session.call_tool.call_count == 1
    assert session.call_tool.call_args_list[0].args[0] == "management__add_mcp_server"


# ---------------------------------------------------------------------------
# autenter=True, pominięty tool → Claude widzi "Pominięto przez użytkownika."
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_autenter_skip_sends_rejection_to_claude():
    """Gdy tool pominięty, do historii trafia 'Pominięto przez użytkownika.'"""
    state["autenter"] = False
    session = _session_with_tool("echo")

    captured = []

    async def fake_create(**kwargs):
        captured.append(list(kwargs["messages"]))
        if len(captured) == 1:
            return _tool_response("echo", tool_id="tid1")
        return _text_response("rozumiem")

    anthropic_mock = MagicMock()
    anthropic_mock.messages.create = fake_create

    call_n = 0

    async def fake_executor(executor, fn):
        nonlocal call_n
        call_n += 1
        if call_n == 1:
            return "zrób coś"
        if call_n == 2:
            return "n"
        raise EOFError()

    with _config_patch(), \
         patch("client.anthropic.AsyncAnthropic", return_value=anthropic_mock), \
         _patch_http(), \
         _patch_session(session), \
         patch("asyncio.get_event_loop") as mock_loop:

        loop_mock = MagicMock()
        mock_loop.return_value = loop_mock
        loop_mock.run_in_executor = fake_executor

        await client.run()

    # Drugie wywołanie Anthropic dostaje tool_result z "Pominięto..."
    assert len(captured) == 2
    tool_results = captured[1][-1]["content"]
    assert tool_results[0]["tool_use_id"] == "tid1"
    assert "Pominięto" in tool_results[0]["content"]
