"""Every source produces the same kind of record (ADR-0010).

The Intake Center's claim is about *documents*, not about WhatsApp. It held only
by accident: registration was written inside the WhatsApp gate, and the manual
submission script started workflows directly — so anything put through it was
invisible to the Intake Center, which is the single condition that record exists
to forbid. Months of manual submissions left no trace.

These assert the property rather than the implementation: whatever arrives, from
wherever, produces an intake record first, carries its reference into the
workflow, and reports the same six states afterwards. A future source — email, a
portal, a scanner — passes these by using the same function, and fails them by
inventing a second path.
"""

from __future__ import annotations

from datetime import UTC, datetime

import pytest

from app.intake import InMemoryOutbox
from app.whatsapp.inbox import InboxFilter
from app.whatsapp.messages import InboundMessage, MediaReference, MessageType
from app.whatsapp.pending import InMemoryPendingPdfs
from app.whatsapp.webhook import InboundQueue
from tests.intake_support import RecordingCms, UnreachableCms

OWNER = "923049637232"


@pytest.fixture
def container(tmp_path):
    class FakeContainer:
        inbound_queue = InboundQueue()
        incoming_dir = tmp_path
        inbox = InboxFilter()
        pending_pdfs = InMemoryPendingPdfs()
        model = None
        cms = RecordingCms()
        intake_outbox = InMemoryOutbox()

        class whatsapp:
            @staticmethod
            def download_media(media, destination):
                destination.write_bytes(b"%PDF-1.4 statement")

                return destination

    return FakeContainer


def forwarded(message_id="msg-1"):
    return InboundMessage(
        provider_message_id=message_id,
        sender=OWNER,
        type=MessageType.DOCUMENT,
        text="",
        media=MediaReference(
            handle="h", mime_type="application/pdf",
            filename="Account Statement.pdf", size_bytes=6910,
        ),
    )


class TestBothSourcesRegisterBeforeAnythingReadsThem:
    def test_a_forwarded_document_registers_as_whatsapp(self, container):
        from app import __main__ as entry

        entry._take_in(container, forwarded())

        assert container.cms.registered[0]["source"] == "whatsapp"

    def test_a_hand_submitted_document_registers_as_manual(self, container, tmp_path):
        from app import __main__ as entry

        path = tmp_path / "by-hand.pdf"
        path.write_bytes(b"%PDF-1.4 submitted by hand")

        reference = entry.register_arrival(
            container,
            source="manual",
            path=path,
            filename=path.name,
            mime_type="application/pdf",
            size_bytes=path.stat().st_size,
        )

        assert reference is not None
        assert container.cms.registered[0]["source"] == "manual"
        assert container.cms.registered[0]["original_filename"] == "by-hand.pdf"

    def test_every_source_gets_an_idempotency_key(self, container, tmp_path):
        from app import __main__ as entry

        path = tmp_path / "scan.pdf"
        path.write_bytes(b"%PDF-1.4 x")

        entry.register_arrival(container, source="manual", path=path, filename=path.name)
        entry.register_arrival(container, source="manual", path=path, filename=path.name)

        keys = [r["provider_message_id"] for r in container.cms.registered]

        # Nothing outside WhatsApp carries one, so one is minted. Without it the
        # CMS deduplicates on NULL — which deduplicates nothing — and two
        # deliveries of one file become two records in front of a reviewer.
        # The stress harness produced exactly that: INT-2808 and INT-2809 for a
        # single document.
        assert all(key.startswith("manual:") for key in keys)
        assert len(set(keys)) == 2, "two separate submissions are two documents"

    def test_the_record_is_the_same_shape_whichever_way_it_came(self, container, tmp_path):
        from app import __main__ as entry

        entry._take_in(container, forwarded())

        path = tmp_path / "by-hand.pdf"
        path.write_bytes(b"%PDF-1.4 submitted by hand")
        entry.register_arrival(
            container, source="manual", path=path, filename=path.name,
            mime_type="application/pdf", size_bytes=path.stat().st_size,
        )

        whatsapp, manual = container.cms.registered

        # Same fields, same meanings. The difference between the two is the
        # source and the presence of a message id — everything the Intake Center
        # renders is common to both.
        for field in ("source", "original_filename", "mime", "size_bytes"):
            assert field in whatsapp
            assert field in manual


class TestBothSourcesRefuseToProceedUnregistered:
    def test_a_forwarded_document_is_held_when_the_cms_is_down(self, container):
        from app import __main__ as entry

        container.cms = UnreachableCms()

        assert entry._take_in(container, forwarded()) is None
        assert len(container.intake_outbox) == 1

    def test_a_manual_document_is_held_too(self, container, tmp_path):
        from app import __main__ as entry

        container.cms = UnreachableCms()

        path = tmp_path / "by-hand.pdf"
        path.write_bytes(b"%PDF-1.4 x")

        reference = entry.register_arrival(
            container, source="manual", path=path, filename=path.name,
        )

        # None means "do not proceed", and the manual path honours it for the
        # same reason the forwarding path does: a document the CMS has never
        # heard of must not be read as though it had been recorded.
        assert reference is None
        assert len(container.intake_outbox) == 1
        assert container.intake_outbox.oldest().source == "manual"

    def test_a_held_manual_document_drains_like_any_other(self, container, tmp_path):
        from app import __main__ as entry

        container.cms = UnreachableCms()

        path = tmp_path / "by-hand.pdf"
        path.write_bytes(b"%PDF-1.4 x")
        entry.register_arrival(container, source="manual", path=path, filename=path.name)

        container.cms = RecordingCms()
        entry._drain_outbox(container)

        # One outbox, one drain, whatever the source.
        assert len(container.intake_outbox) == 0
        assert container.cms.registered[0]["source"] == "manual"


class TestBothSourcesReportTheSameLifecycle:
    def _run(self, state, skipped=False):
        from app.workflow.state import RunState, StepState

        class Step:
            name = "file"
            state = StepState.SKIPPED if skipped else StepState.SUCCEEDED

        class Run:
            id = "run-1"
            steps = [Step()]

        Run.state = RunState(state)

        return Run()

    @pytest.mark.parametrize("source", ["whatsapp", "manual"])
    def test_the_same_outcome_maps_to_the_same_state(self, container, tmp_path, source):
        from app import __main__ as entry

        path = tmp_path / "d.pdf"
        path.write_bytes(b"%PDF-1.4 x")

        reference = entry.register_arrival(
            container, source=source, path=path, filename=path.name,
        )

        class Tracking(RecordingCms):
            def __init__(self):
                super().__init__()
                self.pushed = []

            def update_intake(self, reference, **fields):
                self.pushed.append(fields.get("status"))

                return {"reference": reference}

        container.cms = Tracking()

        entry._push_outcome(container, reference, self._run("awaiting_approval"), "document_intake")

        # The lifecycle a person sees does not depend on how the file arrived.
        assert container.cms.pushed == ["approval"]

    @pytest.mark.parametrize("source", ["whatsapp", "manual"])
    def test_a_document_naming_nobody_waits_whatever_its_source(self, container, tmp_path, source):
        from app import __main__ as entry

        path = tmp_path / "d.pdf"
        path.write_bytes(b"%PDF-1.4 x")

        reference = entry.register_arrival(
            container, source=source, path=path, filename=path.name,
        )

        class Tracking(RecordingCms):
            def __init__(self):
                super().__init__()
                self.pushed = []

            def update_intake(self, reference, **fields):
                self.pushed.append(fields.get("status"))

                return {"reference": reference}

        container.cms = Tracking()

        entry._push_outcome(
            container, reference, self._run("awaiting_approval", skipped=True), "document_intake",
        )

        assert container.cms.pushed == ["waiting"]
