"""Tests for handler registration and dispatch."""

import logging

import pytest

from entr_adapter_core.app import Dispatcher
from entr_adapter_core.handlers import BaseHandler, RetrievalHandler
from entr_adapter_core.schemas import (
    AdapterResponse,
    DocumentDownloadResponse,
    DocumentPayload,
    RetrievalPayload,
    RetrievalResponse,
    StatusEnum,
)


# -- Test handler implementations --


class SuccessHandler(BaseHandler):
    async def process(self, payload: DocumentPayload) -> AdapterResponse:
        return AdapterResponse(
            document_id=payload.document_id,
            status=StatusEnum.SUCCESS,
        )


class FailingHandler(BaseHandler):
    async def process(self, payload: DocumentPayload) -> AdapterResponse:
        raise RuntimeError("Something went wrong")


class MockRetrievalHandler(RetrievalHandler):
    async def list_documents(self, payload: RetrievalPayload) -> RetrievalResponse:
        return RetrievalResponse(
            status=StatusEnum.SUCCESS,
            documents=[],
        )

    async def get_document(self, document_identifier: str) -> DocumentDownloadResponse:
        return DocumentDownloadResponse(
            document_identifier="test.pdf",
            adapter_document_id=document_identifier,
            content_b64="dGVzdA==",
        )


class HandlerWithRetrieval(BaseHandler):
    async def process(self, payload: DocumentPayload) -> AdapterResponse:
        return AdapterResponse(
            document_id=payload.document_id,
            status=StatusEnum.SUCCESS,
        )


# -- Fixtures --


@pytest.fixture
def payload():
    return DocumentPayload(
        adapter_identifier="invoice",
        document_id=42,
        data={"total": 100},
    )


@pytest.fixture
def retrieval_payload():
    return RetrievalPayload(source_identifier="invoice", date_from="2026-01-01")


@pytest.fixture
def dispatcher():
    return Dispatcher(handlers={"invoice": SuccessHandler()})


@pytest.fixture
def dispatcher_with_retrieval():
    retrieval = MockRetrievalHandler()
    handler = HandlerWithRetrieval(retrieval_handler=retrieval)
    return Dispatcher(handlers={"invoice": handler, "plain": SuccessHandler()})


# -- Registration validation tests --


class TestRegistration:

    def test_empty_handlers_raises_value_error(self):
        with pytest.raises(ValueError, match="At least one handler"):
            Dispatcher(handlers={})

    def test_non_handler_value_raises_type_error(self):
        with pytest.raises(TypeError, match="must be an instance of BaseHandler"):
            Dispatcher(handlers={"bad": "not a handler"})

    def test_valid_registration(self):
        d = Dispatcher(handlers={"invoice": SuccessHandler()})
        assert "invoice" in d.get_handler_identifiers()

    def test_get_handler_identifiers(self):
        d = Dispatcher(handlers={
            "invoice": SuccessHandler(),
            "credit_note": SuccessHandler(),
        })
        ids = d.get_handler_identifiers()
        assert set(ids) == {"invoice", "credit_note"}


# -- Process dispatch tests --


class TestDispatchProcess:

    @pytest.mark.asyncio
    async def test_dispatch_to_correct_handler(self, dispatcher, payload):
        response = await dispatcher.dispatch_process(payload)
        assert isinstance(response, AdapterResponse)
        assert response.document_id == 42
        assert response.status == StatusEnum.SUCCESS

    @pytest.mark.asyncio
    async def test_unknown_identifier_returns_failed(self, dispatcher):
        payload = DocumentPayload(
            adapter_identifier="unknown",
            document_id=99,
            data={},
        )
        response = await dispatcher.dispatch_process(payload)
        assert response.status == StatusEnum.FAILED
        assert "Unknown adapter_identifier: unknown" in response.failure_reason

    @pytest.mark.asyncio
    async def test_exception_returns_failed_with_error_message(self):
        d = Dispatcher(handlers={"broken": FailingHandler()})
        payload = DocumentPayload(
            adapter_identifier="broken",
            document_id=1,
            data={},
        )
        response = await d.dispatch_process(payload)
        assert response.status == StatusEnum.FAILED
        assert "Something went wrong" in response.failure_reason


# -- Retrieval dispatch tests --


class TestDispatchRetrieval:

    @pytest.mark.asyncio
    async def test_list_documents_with_retrieval_handler(
        self, dispatcher_with_retrieval, retrieval_payload
    ):
        response = await dispatcher_with_retrieval.dispatch_list_documents(
            "invoice", retrieval_payload
        )
        assert isinstance(response, RetrievalResponse)
        assert response.status == StatusEnum.SUCCESS

    @pytest.mark.asyncio
    async def test_list_documents_without_retrieval_handler(
        self, dispatcher_with_retrieval, retrieval_payload
    ):
        response = await dispatcher_with_retrieval.dispatch_list_documents(
            "plain", retrieval_payload
        )
        assert response.status == StatusEnum.FAILED
        assert "No retrieval handler" in response.message

    @pytest.mark.asyncio
    async def test_list_documents_unknown_identifier(
        self, dispatcher_with_retrieval, retrieval_payload
    ):
        response = await dispatcher_with_retrieval.dispatch_list_documents(
            "nonexistent", retrieval_payload
        )
        assert response.status == StatusEnum.FAILED
        assert "Unknown source_identifier" in response.message

    @pytest.mark.asyncio
    async def test_download_document_with_retrieval_handler(
        self, dispatcher_with_retrieval
    ):
        response = await dispatcher_with_retrieval.dispatch_download_document(
            "invoice", "doc-123"
        )
        assert isinstance(response, DocumentDownloadResponse)
        assert response.adapter_document_id == "doc-123"

    @pytest.mark.asyncio
    async def test_download_document_without_retrieval_handler(
        self, dispatcher_with_retrieval
    ):
        with pytest.raises(ValueError, match="No retrieval handler"):
            await dispatcher_with_retrieval.dispatch_download_document(
                "plain", "doc-123"
            )

    @pytest.mark.asyncio
    async def test_download_document_unknown_identifier(
        self, dispatcher_with_retrieval
    ):
        with pytest.raises(ValueError, match="Unknown source_identifier"):
            await dispatcher_with_retrieval.dispatch_download_document(
                "nonexistent", "doc-123"
            )


# -- Flight recorder integration smoke tests --


class LoggingHandler(BaseHandler):
    """Handler that emits log messages for flight recorder testing."""

    async def process(self, payload: DocumentPayload) -> AdapterResponse:
        logger = logging.getLogger(__name__)
        logger.info("Processing document %d", payload.document_id)
        logger.debug("Debug info for document %d", payload.document_id)
        return AdapterResponse(
            document_id=payload.document_id,
            status=StatusEnum.SUCCESS,
        )


class VerboseHandler(BaseHandler):
    """Handler that emits many log messages for truncation testing."""

    async def process(self, payload: DocumentPayload) -> AdapterResponse:
        logger = logging.getLogger(__name__)
        for i in range(50):
            logger.info("Log entry %d", i)
        return AdapterResponse(
            document_id=payload.document_id,
            status=StatusEnum.SUCCESS,
        )


class TestDispatchFlightRecorder:

    @pytest.mark.asyncio
    async def test_dispatch_wraps_with_flight_recorder(self):
        d = Dispatcher(handlers={"test": LoggingHandler()})
        payload = DocumentPayload(
            adapter_identifier="test", document_id=1, data={}
        )
        response = await d.dispatch_process(payload)
        assert response.execution_logs is not None
        assert isinstance(response.execution_logs, list)

    @pytest.mark.asyncio
    async def test_dispatch_captures_handler_logs(self):
        d = Dispatcher(handlers={"test": LoggingHandler()})
        payload = DocumentPayload(
            adapter_identifier="test", document_id=42, data={}
        )
        response = await d.dispatch_process(payload)
        messages = [
            entry["message"] for entry in response.execution_logs
            if "message" in entry
        ]
        assert any("Processing document 42" in m for m in messages)

    @pytest.mark.asyncio
    async def test_dispatch_catches_unhandled_exception(self):
        d = Dispatcher(handlers={"broken": FailingHandler()})
        payload = DocumentPayload(
            adapter_identifier="broken", document_id=1, data={}
        )
        response = await d.dispatch_process(payload)
        assert response.status == StatusEnum.FAILED
        assert "Something went wrong" in response.failure_reason
        assert response.execution_logs is not None
        # Traceback IS captured in execution_logs for diagnostics
        all_text = " ".join(
            entry.get("message", "") for entry in response.execution_logs
        )
        assert "Something went wrong" in all_text

    @pytest.mark.asyncio
    async def test_dispatch_flight_recorder_truncation(self):
        from entr_adapter_core.logging import execute_with_flight_recorder

        d = Dispatcher(handlers={"verbose": VerboseHandler()})
        payload = DocumentPayload(
            adapter_identifier="verbose", document_id=1, data={}
        )
        # Use a very small max_entries to force truncation
        # We test execute_with_flight_recorder directly since dispatcher
        # uses the default limit
        handler = VerboseHandler()
        response = await execute_with_flight_recorder(
            handler, payload, max_entries=10
        )
        assert response.execution_logs is not None
        # The last entry should be the truncation marker
        last_entry = response.execution_logs[-1]
        assert last_entry.get("truncated") is True
        assert last_entry.get("dropped_count") > 0
