from __future__ import annotations

from types import SimpleNamespace

from agent_framework.channels.interruption import evaluate_interruption
from agent_framework.guardrails.calibrated.contracts import GuardRailContext
from agent_framework.guardrails.calibrated.rails.anatel import AnatelRail

from app.domain.contas.vas_variation import VariedCharge, varied_current_charges
from app.domain.contas.workflow_actions import build_contas_workflow_actions


PAST = "2026-03-10T00:00:00.000Z"
CURRENT = "2026-04-07T00:00:00.000Z"


def _group(*items: dict, type_: str = "servicos_contratados_de_parceiros") -> dict:
    return {"type": type_, "desc": "Serviços de valor adicionado", "items": list(items)}


def test_vas_variation_preserva_multiset_e_direcao_atual():
    payload = {"invoiceVariation": [
        _group(
            {"desc": "Qualifica Mensal", "value": "19.99", "invoice": PAST},
            {"desc": "Qualifica Mensal", "value": "19.99", "invoice": CURRENT},
            {"desc": "Qualifica Mensal", "value": "19.99", "invoice": CURRENT},
        )
    ]}
    result = varied_current_charges(payload)
    assert result == (VariedCharge(desc="Qualifica Mensal", value=result[0].value),)
    assert str(result[0].value) == "19.99"


def test_vas_variation_streaming_nao_entra_no_recorte_avulso_padrao():
    payload = {"invoiceVariation": [
        _group(
            {"desc": "Netflix Padrão", "value": "20.90", "invoice": CURRENT},
            type_="streaming",
        )
    ]}
    assert varied_current_charges(payload) == ()


def test_post_finalize_replay_e_short_circuit_framework():
    decision = evaluate_interruption(
        payload={},
        message_text="alô, ainda está aí?",
        session_metadata={
            "conversation_closed": True,
            "terminal_status": "nao_resolvido",
            "last_assistant_text": "Aguarde um instante na linha. Protocolo: 2026492272073",
            "last_assistant_is_interruptible": True,
        },
    )
    assert decision.action == "replay"
    assert decision.reason == "post_finalize"
    assert "2026492272073" in decision.replay_text
    assert decision.is_interruptible is False


def test_anatel_rail_e_framework_native_sem_dependencia_legado():
    rail = AnatelRail()
    blocked = rail.evaluate(
        GuardRailContext(
            session_id="s1",
            user_text="O ajuste foi concluído.",
            agent_metadata={
                "tipo_fluxo": "ajuste",
                "requer_protocolo": True,
                "expected_protocols": ["2026492272073"],
            },
        )
    )
    assert blocked.allowed is False
    assert blocked.sanitized_text
    assert "protocolo" in blocked.sanitized_text.lower()

    approved = rail.evaluate(
        GuardRailContext(
            session_id="s1",
            user_text="Ajuste concluído. Protocolo: 2026492272073.",
            agent_metadata={"tipo_fluxo": "ajuste"},
        )
    )
    assert approved.allowed is True


class _FakeClient:
    def __init__(self):
        self.contestar_calls = 0

    def billing_analysis(self, msisdn: str):
        return {
            "Servicos de Valor Adicionado": [
                {"desc": "Pacote Musica", "valor": "10,00"}
            ]
        }

    def contestar(self, payload):
        self.contestar_calls += 1
        return {"ok": True}


class _FakeService:
    def __init__(self):
        self.client = _FakeClient()


def test_cval_bloqueia_contestacao_antes_do_side_effect():
    service = _FakeService()
    reg = build_contas_workflow_actions(service)  # type: ignore[arg-type]
    action = reg.get("abrir_contestacao_cliente")
    result = action(
        {
            "msisdn": "11999999999",
            "items": [
                {
                    "itemName": "Pacote nao cobrado",
                    "claimedAmount": "10",
                    "validatedAmount": "10",
                }
            ],
        },
        {"input": {}},
    )
    assert result["success"] is False
    assert result["blocked"] is True
    assert result["guardrail_code"] == "CVAL"
    assert service.client.contestar_calls == 0

from app.domain.contas.item_matcher import SimilarityItemMatcher


def test_similarity_matcher_recupera_erros_de_transcricao_comuns():
    matcher = SimilarityItemMatcher()
    candidates = [
        "Tamboro Mensal",
        "Netflix Padrão",
        "Truecaller Semanal",
        "MasterChef Mensal",
        "Paramount+ Mensal",
    ]
    assert matcher.match("mestershef", candidates)[0] == "MasterChef Mensal"
    assert matcher.match("trucaller", candidates)[0] == "Truecaller Semanal"
    assert matcher.match("paramaunt", candidates)[0] == "Paramount+ Mensal"


def test_tool_clarification_aceita_opcao_ordinal():
    from agent_framework.runtime.agent_runtime import AgentRuntimeMixin

    options = [
        {"label": "Tamboro Mensal — linha final 1111", "value": "Tamboro Mensal"},
        {"label": "Tamboro Mensal — linha final 2222", "value": "Tamboro Mensal"},
    ]
    selected = AgentRuntimeMixin._choose_tool_clarification_option("2", options)
    assert selected == options[1]
    selected_by_name = AgentRuntimeMixin._choose_tool_clarification_option(
        "Tamboro Mensal — linha final 1111", options
    )
    assert selected_by_name == options[0]


def test_output_guardrail_context_extrai_protocolos_das_tools():
    from app.workflows.agent_graph import AgentWorkflow

    ctx = AgentWorkflow._output_guardrail_context(
        {
            "context": {},
            "mcp_results": [
                {
                    "ok": True,
                    "result": {
                        "result": {
                            "protocolo_id": "2026492272073",
                            "nested": {"protocolNumber": "2026492272074"},
                        }
                    },
                }
            ],
        }
    )
    assert ctx["requer_protocolo"] is True
    assert ctx["tipo_fluxo"] == "ajuste"
    assert ctx["expected_protocols"] == ["2026492272073", "2026492272074"]
    assert ctx["tool_executed"] is True


def test_fix_whole_utterance_transcription_preserva_fronteira_de_frase():
    from agent_framework.channels.transcription import fix_whole_utterance_transcription

    assert fix_whole_utterance_transcription("Fim") == "Sim"
    assert fix_whole_utterance_transcription("Mim?") == "Sim"
    assert fix_whole_utterance_transcription("chegou ao fim") == "chegou ao fim"
    assert fix_whole_utterance_transcription("isso é pra mim") == "isso é pra mim"


def test_post_finalize_replay_preserva_terminal_status_e_fallback_sem_historico():
    decision = evaluate_interruption(
        payload={},
        message_text="oi",
        session_metadata={
            "conversation_closed": True,
            "terminal_status": "nao_resolvido",
            "last_assistant_text": "",
            "terminal_replay_text": "",
        },
        terminal_fallback_text=(
            "Por aqui finalizamos o tratamento da sua solicitação. "
            "Aguarde um instante na linha."
        ),
    )
    assert decision.action == "replay"
    assert decision.reason == "post_finalize"
    assert decision.terminal_status == "nao_resolvido"
    assert decision.replay_text.startswith("Por aqui finalizamos")
    assert decision.is_interruptible is False


def test_post_finalize_replay_status_invalido_usa_fallback_seguro():
    decision = evaluate_interruption(
        payload={},
        message_text="oi",
        session_metadata={"conversation_closed": True},
        terminal_fallback_text="Atendimento já encerrado.",
    )
    assert decision.action == "replay"
    assert decision.terminal_status == "erro_falha_sistema"
    assert decision.replay_text == "Atendimento já encerrado."


import pytest


@pytest.mark.parametrize(
    ("entrada", "esperado"),
    [
        ("Fim", "Sim"),
        ("fim", "Sim"),
        ("FIM", "Sim"),
        ("Fim.", "Sim"),
        ("  fim  ", "Sim"),
        ("fim!", "Sim"),
        ("Mim", "Sim"),
        ("mim", "Sim"),
        ("Mim?", "Sim"),
        ("chegou ao fim", "chegou ao fim"),
        ("isso é pra mim", "isso é pra mim"),
        ("fim de semana", "fim de semana"),
        ("Fim, pode encerrar", "Fim, pode encerrar"),
        ("mim mesmo", "mim mesmo"),
        ("Sim", "Sim"),
        ("não", "não"),
        ("", ""),
        ("   ", "   "),
        ("cancela o Tamboro", "cancela o Tamboro"),
    ],
)
def test_transcription_fix_paridade_original(entrada: str, esperado: str):
    from agent_framework.channels.transcription import fix_whole_utterance_transcription

    assert fix_whole_utterance_transcription(entrada) == esperado


class _ClassifierResponse:
    def __init__(self, content: str):
        self.content = content


class _ClassifierLLM:
    def __init__(self, content: str = "0", *, fail: bool = False):
        self.content = content
        self.fail = fail
        self.calls = []

    async def ainvoke(self, messages, **kwargs):
        self.calls.append((messages, kwargs))
        if self.fail:
            raise RuntimeError("classifier unavailable")
        return _ClassifierResponse(self.content)


def test_processing_interruption_interrompivel_pede_classificacao():
    decision = evaluate_interruption(
        payload={"processing_interruption": {"heard_text": "cancelar também"}},
        message_text="cancelar também",
        session_metadata={
            "last_assistant_text": "Vou verificar sua fatura.",
            "last_assistant_is_interruptible": True,
        },
    )
    assert decision.action == "classify"
    assert decision.replay_text == "Vou verificar sua fatura."
    assert decision.heard_text == "cancelar também"


@pytest.mark.asyncio
async def test_processing_interruption_classifier_resultado_1_reprocessa():
    from agent_framework.channels.interruption import classify_processing_interruption

    llm = _ClassifierLLM("1")
    result = await classify_processing_interruption(
        llm,
        original_agent="Vou orientar sobre o cancelamento.",
        original_client="Quero cancelar VOD e o",
        supplement_client="Aluguel de Filme 1.",
    )
    assert result is True
    assert llm.calls
    _, kwargs = llm.calls[0]
    assert kwargs["profile_name"] == "processing_interruption_classifier"
    assert kwargs["component_name"] == "processing_interruption_classifier"


@pytest.mark.asyncio
async def test_processing_interruption_classifier_falha_faz_replay_seguro():
    from agent_framework.channels.interruption import classify_processing_interruption

    llm = _ClassifierLLM(fail=True)
    result = await classify_processing_interruption(
        llm,
        original_agent="Aqui vai a resposta.",
        original_client="quanto custa?",
        supplement_client="oi",
    )
    assert result is False



def test_finalizacao_informacional_infer_tipo_pela_fatura():
    from app.domain.contas.workflow_actions import _infer_informational_vas_types

    invoice_detail = {
        "11999999999": {
            "Serviços Bundle Inclusos": [{"desc": "EXA Cloud", "value": 0}],
            "Serviços Contratados de Terceiros": [{"desc": "Netflix Premium", "value": 59.9}],
            "SVA Detalhe Total": [{"desc": "Aluguel de Filme 2", "value": 19.9}],
        }
    }
    assert _infer_informational_vas_types(
        ["EXA Cloud", "Netflix Premium", "Aluguel de Filme 2"], invoice_detail
    ) == {"bundle", "estrategico", "avulso"}
    assert _infer_informational_vas_types(["exa clod"], invoice_detail) == {"bundle"}


def test_finalizacao_informacional_infer_tipo_de_billing_analysis_json():
    import json
    from app.domain.contas.workflow_actions import _infer_informational_vas_types
    payload = {
        "currentInvoice": [
            {"desc": "Serviços de valor adicionado", "items": [
                {"contestable": True, "desc": "Aluguel de Filme 3", "type": "servicos_contratados_de_parceiros"},
                {"contestable": False, "desc": "VOD + Canais Abertos", "type": "servicos_contratados_de_parceiros"},
            ]},
            {"desc": "Streamings", "items": [
                {"contestable": False, "desc": "YouTube Premium Mensal", "type": "streaming"}
            ]},
        ]
    }
    assert _infer_informational_vas_types(
        ["YouTube Premium Mensal", "Aluguel de Filme 3"], json.dumps(payload, ensure_ascii=False)
    ) == {"estrategico", "avulso"}


def test_output_guardrail_context_inclui_pedido_atual_para_aoferta():
    from app.workflows.agent_graph import AgentWorkflow

    ctx = AgentWorkflow._output_guardrail_context(
        {
            "context": {},
            "history": [
                {"role": "assistant", "content": "O TIM Music custa R$ 12,90."},
            ],
            "user_text": "Quero cancelar o TIM Music",
            "mcp_results": [],
        }
    )
    assert ctx["conversation_history"][-1] == {
        "role": "user",
        "content": "Quero cancelar o TIM Music",
    }
    assert ctx["history_texts"][-1] == "Quero cancelar o TIM Music"


def test_output_guardrail_context_nao_duplica_user_text_ja_no_historico():
    from app.workflows.agent_graph import AgentWorkflow

    ctx = AgentWorkflow._output_guardrail_context(
        {
            "context": {},
            "history": [{"role": "user", "content": "Quero cancelar o TIM Music"}],
            "user_text": "Quero cancelar o TIM Music",
            "mcp_results": [],
        }
    )
    assert len(ctx["conversation_history"]) == 1


def test_output_guardrail_context_isola_transacao_terminal_apos_intent_shift():
    from app.workflows.agent_graph import AgentWorkflow

    ctx = AgentWorkflow._output_guardrail_context(
        {
            "context": {
                "transaction_pre_validation": {
                    "status": "OUT_OF_SCOPE",
                    "terminal": True,
                    "error": "TIM CTRL Redes Sociais 8.0",
                },
            },
            "history": [
                {"role": "user", "content": "nao contratei TIM CTRL Redes Sociais 8.0"},
                {"role": "assistant", "content": "Esse item nao e contestavel."},
            ],
            "user_text": "quais sao meus servicos",
            "route": "vas_agent",
            "intent": "contas_vas_information",
            "route_decision": {
                "route": "vas_agent",
                "intent": "contas_vas_information",
                "metadata": {
                    "route_stickiness_preempted": True,
                    "previous_agent": "contestacao_agent",
                    "previous_intent": "contas_contestation",
                },
            },
            "transaction_status": "OUT_OF_SCOPE",
            "transaction_pre_validation": {
                "status": "OUT_OF_SCOPE",
                "terminal": True,
                "error": "TIM CTRL Redes Sociais 8.0",
            },
            "mcp_results": [],
        }
    )

    assert ctx["conversation_history"] == [
        {"role": "user", "content": "quais sao meus servicos"}
    ]
    assert ctx["history_texts"] == ["quais sao meus servicos"]
    assert ctx["current_user_message"] == "quais sao meus servicos"
    assert ctx["current_route"] == "vas_agent"
    assert ctx["current_intent"] == "contas_vas_information"
    assert ctx["historical_transaction_ignored"] is True
    assert ctx["historical_transaction_status"] == "OUT_OF_SCOPE"
    assert "transaction_pre_validation" not in ctx
    assert "TIM CTRL Redes Sociais" not in str(ctx)


def test_output_guardrail_context_preserva_historico_no_turno_terminal_sem_intent_shift():
    from app.workflows.agent_graph import AgentWorkflow

    ctx = AgentWorkflow._output_guardrail_context(
        {
            "context": {},
            "history": [
                {"role": "user", "content": "nao contratei TIM CTRL Redes Sociais 8.0"},
            ],
            "user_text": "o valor e 71,99",
            "route": "contestacao_agent",
            "intent": "state:COLLECTING_CONTESTACAO_PARAMETERS",
            "route_decision": {
                "route": "contestacao_agent",
                "intent": "state:COLLECTING_CONTESTACAO_PARAMETERS",
                "metadata": {},
            },
            "transaction_status": "OUT_OF_SCOPE",
            "transaction_pre_validation": {
                "status": "OUT_OF_SCOPE",
                "terminal": True,
            },
            "mcp_results": [],
        }
    )

    assert ctx["history_texts"] == [
        "nao contratei TIM CTRL Redes Sociais 8.0",
        "o valor e 71,99",
    ]
    assert "historical_transaction_ignored" not in ctx


def test_vas_prompt_nao_expoe_capacidade_cancelamento_em_consulta_informativa():
    from pathlib import Path

    prompt = Path("config/prompts/vas.yaml").read_text(encoding="utf-8")
    assert "não exponha capacidades transacionais" in prompt
    assert "can.cancel" in prompt
    assert '"Cancelamento disponível"' in prompt
    assert "a menos que o cliente tenha perguntado explicitamente" in prompt
