from __future__ import annotations

import pytest

from app.extensions.tim_guardrails import TimProactiveOfferRail


class _FailIfCalledLLM:
    async def ainvoke(self, *args, **kwargs):
        raise AssertionError('TIM_AOFERTA LLM must not run for transaction continuation')


class _BlockingLLM:
    def __init__(self):
        self.calls = 0

    async def ainvoke(self, *args, **kwargs):
        self.calls += 1
        return '{"allowed": false, "reason": "oferta proativa"}'


@pytest.mark.asyncio
@pytest.mark.parametrize('status', ['COLLECTING_PARAMETERS', 'AWAITING_CONFIRMATION'])
async def test_tim_aoferta_bypasses_native_transaction_continuation_states(status):
    decision = await TimProactiveOfferRail().evaluate(
        'Você confirma o cancelamento do serviço TIM Fashion?',
        {
            'transaction_status': status,
            'guardrail_llm': _FailIfCalledLLM(),
        },
    )

    assert decision.allowed is True
    assert decision.code == 'TIM_AOFERTA'
    assert decision.reason == f'continuidade_transacional:{status}'
    assert decision.metadata['mechanism'] == 'deterministic_transaction_bypass'
    assert decision.metadata['transaction_status'] == status


@pytest.mark.asyncio
async def test_tim_aoferta_detects_awaiting_confirmation_from_mcp_results():
    decision = await TimProactiveOfferRail().evaluate(
        'Você confirma o cancelamento do serviço TIM Fashion?',
        {
            'mcp_results': [
                {
                    'tool_name': 'cancelar_vas_avulso',
                    'awaiting_confirmation': True,
                    'transaction_status': 'AWAITING_CONFIRMATION',
                }
            ],
            'guardrail_llm': _FailIfCalledLLM(),
        },
    )

    assert decision.allowed is True
    assert decision.metadata['transaction_status'] == 'AWAITING_CONFIRMATION'


@pytest.mark.asyncio
async def test_tim_aoferta_detects_parameter_collection_from_tool_result():
    decision = await TimProactiveOfferRail().evaluate(
        'Para prosseguir, informe valor.',
        {
            'tool_result': [
                {
                    'tool_name': 'contestar_cobranca',
                    'transaction_status': 'COLLECTING_PARAMETERS',
                }
            ],
            'guardrail_llm': _FailIfCalledLLM(),
        },
    )

    assert decision.allowed is True
    assert decision.metadata['transaction_status'] == 'COLLECTING_PARAMETERS'


@pytest.mark.asyncio
async def test_tim_aoferta_still_uses_llm_outside_transaction_continuation():
    llm = _BlockingLLM()
    decision = await TimProactiveOfferRail().evaluate(
        'Caso queira, posso cancelar outro serviço.',
        {
            'transaction_status': 'COMPLETED',
            'guardrail_llm': llm,
        },
    )

    assert llm.calls == 1
    assert decision.allowed is False
    assert decision.reason == 'oferta proativa'


@pytest.mark.asyncio
async def test_tim_aoferta_allows_structurally_authorized_human_handoff_without_calling_llm():
    decision = await TimProactiveOfferRail().evaluate(
        'Vou encaminhar seu atendimento para uma pessoa.',
        {
            'current_route': 'human_handoff',
            'current_intent': 'human_handoff',
            'session_control': 'HUMAN_HANDOFF',
            'human_handoff_requested': True,
            'guardrail_llm': _FailIfCalledLLM(),
        },
    )

    assert decision.allowed is True
    assert decision.reason == 'handoff_humano_autorizado'
    assert decision.metadata['mechanism'] == 'deterministic_handoff_bypass'


@pytest.mark.asyncio
async def test_tim_aoferta_does_not_bypass_transfer_phrase_without_structural_handoff():
    llm = _BlockingLLM()
    decision = await TimProactiveOfferRail().evaluate(
        'Vou encaminhar seu atendimento para uma pessoa.',
        {
            'current_route': 'faturas_agent',
            'current_intent': 'contas_invoice_query',
            'guardrail_llm': llm,
        },
    )

    assert llm.calls == 1
    assert decision.allowed is False

@pytest.mark.asyncio
async def test_tim_aoferta_allows_terminal_handoff_from_resumed_workflow_without_router_handoff():
    decision = await TimProactiveOfferRail().evaluate(
        'Para continuar com a sua solicitação, aguarde um instante.',
        {
            'current_route': 'faturas_agent',
            'current_intent': 'contas_invoice_explanation',
            'mcp_results': [{
                'tool_name': 'retomar_workflow',
                'result': {
                    'status': 'COMPLETED',
                    'output': {
                        'mensagem': 'Para continuar com a sua solicitação, aguarde um instante.',
                        'session_control': 'HUMAN_HANDOFF',
                        'human_handoff_requested': True,
                        'handoff': True,
                        'session_ended': True,
                        'terminal_status': 'human_handoff',
                    },
                },
            }],
            'guardrail_llm': _FailIfCalledLLM(),
        },
    )

    assert decision.allowed is True
    assert decision.reason == 'handoff_humano_autorizado'
    assert decision.metadata['mechanism'] == 'deterministic_handoff_bypass'


@pytest.mark.asyncio
async def test_tim_aoferta_does_not_trust_non_terminal_workflow_handoff_flag():
    llm = _BlockingLLM()
    decision = await TimProactiveOfferRail().evaluate(
        'Vou encaminhar seu atendimento para uma pessoa.',
        {
            'current_route': 'faturas_agent',
            'current_intent': 'contas_invoice_explanation',
            'mcp_results': [{
                'result': {
                    'output': {
                        'handoff': True,
                    },
                },
            }],
            'guardrail_llm': llm,
        },
    )

    assert llm.calls == 1
    assert decision.allowed is False

class _CaptureAllowLLM:
    def __init__(self):
        self.prompt = ''

    async def ainvoke(self, messages, **kwargs):
        self.prompt = messages[-1]['content']
        return '{"allowed": true, "reason": ""}'


@pytest.mark.asyncio
async def test_tim_aoferta_terminal_fallback_receives_compact_authoritative_transaction_context():
    llm = _CaptureAllowLLM()
    huge = {'noise': 'x' * 50000}
    decision = await TimProactiveOfferRail().evaluate(
        'Não consegui cancelar o Paramount+ por aqui. Use o canal oficial para concluir esse mesmo cancelamento.',
        {
            'transaction_status': 'COMPLETED',
            'current_user_message': 'isso mesmo, pode cancelar',
            'conversation_history': [
                {'role': 'user', 'content': 'quero cancelar Tamboro e Paramount+'},
                {'role': 'assistant', 'content': 'Você confirma o cancelamento de Tamboro e Paramount+?'},
                {'role': 'user', 'content': 'isso mesmo, pode cancelar'},
            ],
            'transaction_pre_validation': {
                'requested_arguments': {'subject': 'Tamboro e Paramount+'},
                'resolved_arguments': {'subject': 'Tamboro Mensal, Paramount+', 'items': [{'name': 'Tamboro Mensal'}, {'name': 'Paramount+'}]},
                'effective_tool_name': 'cancelar_vas_avulso',
            },
            'mcp_results': [
                huge,
                {
                    'tool_name': 'cancelar_vas_avulso',
                    'ok': True,
                    'result': {
                        'status': 'COMPLETED',
                        'output': {
                            'results': [
                                {'subject': 'Tamboro Mensal', 'success': True},
                                {'subject': 'Paramount+', 'success': False, 'reason': 'service_not_found'},
                            ]
                        },
                    },
                },
            ],
            'guardrail_llm': llm,
        },
    )

    assert decision.allowed is True
    assert decision.metadata['mechanism'] == 'llm_semantic_compact_transaction_context'
    assert 'Paramount+' in llm.prompt
    assert 'service_not_found' in llm.prompt
    assert 'isso mesmo, pode cancelar' in llm.prompt
    assert len(llm.prompt) < 30000
