from __future__ import annotations

import pytest

from agent_framework.cache.cache import InMemoryCache
from app.domain.contas.invoice_context import InvoiceContextService


class Client:
    def __init__(self):
        self.complete_calls = 0
        self.billing_calls = 0
        self.pdf_calls = 0

    def consultar_faturas(self, msisdn):
        self.complete_calls += 1
        return {
            "billingProfile": {"customer": {"customerId": "C1"}},
            "paymentItems": [{"invoiceId": "I1"}],
        }

    def billing_analysis(self, msisdn, **kwargs):
        self.billing_calls += 1
        return {"invoiceExplanation": "explicacao"}

    def bill_pdf(self, msisdn, invoice_id, customer_id, **kwargs):
        self.pdf_calls += 1
        return {
            "status": "SUCCESS",
            "parsed_content": {
                "total_geral": 191.97,
                "Fatura Resumo": [
                    {"desc": "Período", "period": "01/04/2026 a 30/04/2026"},
                    {"desc": "Emissão", "emissao": "01/04/2026"},
                ],
            },
        }


@pytest.mark.asyncio
async def test_invoice_context_reusa_cache_na_mesma_sessao():
    client = Client()
    svc = InvoiceContextService(client, InMemoryCache(), ttl_seconds=60)  # type: ignore[arg-type]
    a = await svc.get(session_id="s1", msisdn="119", include_detail=True)
    b = await svc.get(session_id="s1", msisdn="119", include_detail=True)
    assert a.cache_hit is False
    assert b.cache_hit is True
    assert client.complete_calls == 1
    assert client.billing_calls == 1
    assert client.pdf_calls == 1


@pytest.mark.asyncio
async def test_invoice_context_isola_cache_por_sessao():
    client = Client()
    svc = InvoiceContextService(client, InMemoryCache(), ttl_seconds=60)  # type: ignore[arg-type]
    await svc.get(session_id="s1", msisdn="119")
    await svc.get(session_id="s2", msisdn="119")
    assert client.complete_calls == 2
    assert client.billing_calls == 2


@pytest.mark.asyncio
async def test_invoice_context_sem_session_nao_compartilha_cache_global():
    client = Client()
    svc = InvoiceContextService(client, InMemoryCache(), ttl_seconds=60)  # type: ignore[arg-type]
    await svc.get(session_id="", msisdn="119")
    await svc.get(session_id="", msisdn="119")
    assert client.complete_calls == 2
    assert client.billing_calls == 2


@pytest.mark.asyncio
async def test_invoice_context_use_cache_false_forca_nova_busca():
    client = Client()
    svc = InvoiceContextService(client, InMemoryCache(), ttl_seconds=60)  # type: ignore[arg-type]
    await svc.get(session_id="s1", msisdn="119")
    await svc.get(session_id="s1", msisdn="119", use_cache=False)
    assert client.complete_calls == 2
    assert client.billing_calls == 2


def test_invoice_explanation_reusa_evidencia_prefetch_sem_nova_api():
    from app.domain.contas.service import ContasDomainService

    class C(Client):
        def consultar_faturas(self, msisdn):
            raise AssertionError("nao deveria consultar novamente")
        def billing_analysis(self, msisdn, **kwargs):
            raise AssertionError("nao deveria consultar novamente")

    service = ContasDomainService(client=C())  # type: ignore[arg-type]
    result = service.invoice_explanation(
        msisdn="119",
        complete_invoices_payload={"paymentItems": []},
        billing_analysis={"invoiceExplanation": "cache"},
    )
    assert result["billing_analysis"]["invoiceExplanation"] == "cache"

@pytest.mark.asyncio
async def test_invoice_context_singleflight_mesma_sessao_nao_duplica_chamadas():
    import asyncio

    class Blocking(Client):
        def __init__(self):
            super().__init__()
            self.release = asyncio.Event()

        def consultar_faturas(self, msisdn):
            import time
            self.complete_calls += 1
            time.sleep(0.08)
            return {
                "billingProfile": {"customer": {"customerId": "C1"}},
                "paymentItems": [{"invoiceId": "I1"}],
            }

        def billing_analysis(self, msisdn, **kwargs):
            import time
            self.billing_calls += 1
            time.sleep(0.08)
            return {"invoiceExplanation": "explicacao"}

    client = Blocking()
    svc = InvoiceContextService(client, InMemoryCache(), ttl_seconds=60)  # type: ignore[arg-type]
    a, b = await asyncio.gather(
        svc.get(session_id="s1", msisdn="119", message_id="m1"),
        svc.get(session_id="s1", msisdn="119", message_id="m1"),
    )
    assert client.complete_calls == 1
    assert client.billing_calls == 1
    assert a.complete_invoices == b.complete_invoices


@pytest.mark.asyncio
async def test_invoice_context_prefetch_emite_cvn_uma_vez_por_sessao():
    client = Client()
    svc = InvoiceContextService(client, InMemoryCache(), ttl_seconds=60)  # type: ignore[arg-type]
    first = await svc.get(session_id="s1", msisdn="119", message_id="msg-1", use_cache=False)
    second = await svc.get(session_id="s1", msisdn="119", message_id="msg-2", use_cache=False)
    assert [x["code"] for x in first.business_events or []] == ["CVN.002", "CVN.006"]
    assert second.business_events == []
    assert first.business_events[0]["payload"]["messageId"] == "msg-1"
    assert first.business_events[1]["payload"]["agentSpecificData"] == '{"billingId": "I1"}'


@pytest.mark.asyncio
async def test_invoice_context_nova_sessao_pode_emitir_cvn_novamente():
    client = Client()
    svc = InvoiceContextService(client, InMemoryCache(), ttl_seconds=60)  # type: ignore[arg-type]
    first = await svc.get(session_id="s1", msisdn="119", use_cache=False)
    second = await svc.get(session_id="s2", msisdn="119", use_cache=False)
    assert [x["code"] for x in first.business_events or []] == ["CVN.002", "CVN.006"]
    assert [x["code"] for x in second.business_events or []] == ["CVN.002", "CVN.006"]


@pytest.mark.asyncio
async def test_invoice_context_billing_analysis_falha_emite_cvn007_sem_perder_complete_invoices():
    class Failing(Client):
        def billing_analysis(self, msisdn, **kwargs):
            self.billing_calls += 1
            raise RuntimeError("HTTP 550")

    client = Failing()
    svc = InvoiceContextService(client, InMemoryCache(), ttl_seconds=60)  # type: ignore[arg-type]
    ctx = await svc.get(session_id="s1", msisdn="119", message_id="msg-fail", use_cache=False)
    assert ctx.complete_invoices is not None
    assert ctx.billing_analysis is None
    assert "billing_analysis" in str(ctx.error)
    assert [x["code"] for x in ctx.business_events or []] == ["CVN.002", "CVN.007"]


@pytest.mark.asyncio
async def test_invoice_context_cache_incompleto_busca_detail_quando_solicitado_depois():
    client = Client()
    svc = InvoiceContextService(client, InMemoryCache(), ttl_seconds=60)  # type: ignore[arg-type]
    await svc.get(session_id="s1", msisdn="119", include_detail=False)
    detailed = await svc.get(session_id="s1", msisdn="119", include_detail=True)
    assert detailed.invoice_detail is not None
    assert client.pdf_calls == 1

@pytest.mark.asyncio
async def test_invoice_context_expoe_timings_e_erros_por_subconsulta():
    class Partial(Client):
        def billing_analysis(self, msisdn, **kwargs):
            self.billing_calls += 1
            raise RuntimeError("indisponivel")

    client = Partial()
    svc = InvoiceContextService(client, InMemoryCache(), ttl_seconds=60)  # type: ignore[arg-type]
    ctx = await svc.get(session_id="s1", msisdn="119", include_detail=True, use_cache=False)
    assert ctx.metadata["cache_hit"] is False
    assert ctx.metadata["fetch_elapsed_ms"] >= 0
    assert {"complete_invoices", "billing_analysis", "invoice_detail"} <= set(ctx.metadata["task_timings"])
    assert "billing_analysis" in ctx.errors


@pytest.mark.asyncio
async def test_invoice_context_cache_hit_expoe_cache_age_sem_republicar_eventos():
    client = Client()
    svc = InvoiceContextService(client, InMemoryCache(), ttl_seconds=60)  # type: ignore[arg-type]
    await svc.get(session_id="s1", msisdn="119")
    cached = await svc.get(session_id="s1", msisdn="119")
    assert cached.cache_hit is True
    assert cached.metadata["cache_hit"] is True
    assert cached.metadata["cache_age_ms"] >= 0
    assert cached.business_events == []


def test_extract_invoice_summary_context_restaura_semantica_do_original():
    from app.domain.contas.invoice_context import extract_invoice_summary_context

    result = extract_invoice_summary_context({
        "parsed_content": {
            "total_geral": 191.97,
            "Fatura Resumo": [
                {"desc": "Período", "period": "01/04/2026 a 30/04/2026"},
                {"desc": "Emissão", "emissao": "01/04/2026"},
            ],
        }
    })
    assert result == {
        "invoice_amount": "191.97",
        "invoice_amount_open": "191.97",
        "invoice_period": "01/04/2026 a 30/04/2026",
        "invoice_emissao": "01/04/2026",
    }


@pytest.mark.asyncio
async def test_invoice_context_detail_expoe_invoice_amount_semantico():
    client = Client()
    svc = InvoiceContextService(client, InMemoryCache(), ttl_seconds=60)  # type: ignore[arg-type]
    ctx = await svc.get(session_id="s1", msisdn="119", include_detail=True)
    assert ctx.invoice_amount == "191.97"
    assert ctx.invoice_amount_open == "191.97"
    assert ctx.invoice_period == "01/04/2026 a 30/04/2026"
    assert ctx.invoice_emissao == "01/04/2026"
    assert ctx.as_dict()["invoice_amount"] == "191.97"


def test_consultar_faturas_reusa_prefetch_e_publica_valor_sem_reconsultar_backend():
    from app.domain.contas.service import ContasDomainService

    class C(Client):
        def consultar_faturas(self, msisdn):
            raise AssertionError("nao deveria consultar novamente")

    svc = ContasDomainService(client=C())  # type: ignore[arg-type]
    result = svc.consultar_faturas(
        msisdn="119",
        complete_invoices_payload={"paymentItems": [{"invoiceId": "I1"}]},
        invoice_id="I1",
        customer_id="C1",
        invoice_amount="191.97",
        invoice_amount_open="191.97",
    )
    assert result["invoice_amount"] == "191.97"
    assert result["invoice_amount_open"] == "191.97"
    assert result["invoice_id"] == "I1"
    assert result["customer_id"] == "C1"

@pytest.mark.asyncio
async def test_mcp_consultar_faturas_enriquece_com_detail_e_valor(monkeypatch):
    from contas_mcp.servers.contas_mcp_server import main

    client = Client()
    context_service = InvoiceContextService(client, InMemoryCache(), ttl_seconds=60)  # type: ignore[arg-type]
    monkeypatch.setattr(main, "get_invoice_context_service", lambda: context_service)

    args = {"msisdn": "119", "session_id": "s1", "message_id": "m1"}
    await main._enrich_invoice_context("consultar_faturas", args)

    assert client.pdf_calls == 1
    assert args["invoice_amount"] == "191.97"
    assert args["invoice_amount_open"] == "191.97"
    assert args["complete_invoices_payload"]["paymentItems"][0]["invoiceId"] == "I1"
