"""What the classifier reports about itself.

These numbers exist to answer two product questions, and the tests are written
around those rather than around the arithmetic:

  * how often does anything have to *read* a document — the cheap stages exist
    so this stays low, and if it does not they are not earning their place; and
  * how often does nothing recognise a document at all — the number that decides
    whether a model fallback is worth building.

The third property is the one easiest to get wrong: nothing yet must report
nothing, not zero.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from app.documents.classifier import Method, classify
from app.documents.signals import Signals
from app.documents.statistics import STATS, ClassificationStats


@pytest.fixture(autouse=True)
def clean_stats():
    STATS.reset()
    yield
    STATS.reset()


@pytest.fixture
def document(tmp_path) -> Path:
    path = tmp_path / "scan.pdf"
    path.write_bytes(b"%PDF")

    return path


class TestNothingYet:
    def test_an_untouched_tally_reports_nothing_rather_than_zero(self):
        # "0% unrecognised" before any document has arrived is a comfortable
        # lie. The CMS renders an absent block as "nothing yet".
        assert ClassificationStats().report() == {}

    def test_the_report_appears_once_something_is_classified(self, document):
        classify(Signals(path=document, caption="Bank Statement"))

        assert STATS.report()["classified"] == 1


class TestWhatItCounts:
    def test_a_captioned_document_is_not_counted_as_read(self, document):
        classify(Signals(path=document, caption="Bank Statement",
                         read_first_page=lambda: "ignored"))

        report = STATS.report()

        assert report["by_method"] == {Method.CAPTION.value: 1}
        assert report["read"] == 0
        assert report["read_rate"] == 0.0

    def test_a_document_only_the_text_could_name_is_counted_as_read(self, document):
        classify(
            Signals(
                path=document,
                filename="scan001.pdf",
                read_first_page=lambda: "SALARY SLIP\nGross Salary 1\nNet Pay 2",
            )
        )

        report = STATS.report()

        assert report["by_method"] == {Method.RULES.value: 1}
        assert report["read"] == 1

    def test_an_unrecognised_document_is_counted_as_unknown(self, document):
        classify(
            Signals(path=document, filename="scan001.pdf",
                    read_first_page=lambda: "Dear Sir, regards.")
        )

        report = STATS.report()

        assert report["unknown"] == 1
        assert report["unknown_rate"] == 1.0

    def test_rates_are_computed_across_the_mix(self, document):
        classify(Signals(path=document, caption="Bank Statement"))
        classify(Signals(path=document, caption="Salary Slip"))
        classify(Signals(path=document, filename="x.pdf", read_first_page=lambda: "nothing"))

        report = STATS.report()

        assert report["classified"] == 3
        assert report["unknown"] == 1
        assert report["unknown_rate"] == round(1 / 3, 3)
        # Both the count and the rate: the count is what stops somebody drawing
        # a conclusion from three documents.
        assert report["read"] == 1

    def test_it_reports_how_long_deciding_takes(self, document):
        classify(Signals(path=document, caption="Bank Statement"))

        assert STATS.report()["mean_ms"] >= 0


class TestTheModelStageIsNotBuilt:
    def test_nothing_is_ever_attributed_to_a_model(self, document):
        """The number Phase 6 is decided on.

        `Method.MODEL` exists so this can be measured before anything is built.
        Until a model stage exists it must never appear, or the figure that
        justifies building one would already be non-zero.
        """
        for caption in ("Bank Statement", "Salary Slip", ""):
            classify(Signals(path=document, caption=caption))

        assert Method.MODEL.value not in STATS.report()["by_method"]
