"""T-RPT oracle equivalence: the reporting services, run over the REAL
migrated sidecar data, must reproduce the diff-harness baseline
(harness/out/e2e-new.json — extracted from this same database and
proven equal to live Supabase by the M2 compare gate).

Months asserted: ALL months in the oracle — which includes the three
biggest (2026-06: 966 entries, 2026-03: 910, 2026-01: 883), more than
the 3-month bar in 06-test-plan §6. Per (month × project) group we
check entry_count, source_seconds, cost, billout, and adjusted billout
to the cent; per month we check the totals card aggregates.

If these fail after a nightly re-migration, the LEGACY data changed
(entries edited/added in the old app) — re-extract the oracle
(harness/extract_new.py --out harness/out/e2e-new.json) and re-run.
"""

import json
from datetime import datetime
from decimal import Decimal
from pathlib import Path

import pytest

from app.services.reporting.entries import entry_totals
from app.services.reporting.filters import EntryFilters
from app.services.reporting.projects import project_summary
from app.services.reporting.summary import MonthWindow, summary_by_project

ORACLE = Path(__file__).resolve().parents[2] / "harness" / "out" / "e2e-new.json"

# The three biggest months (the ACCEPTANCE.md side-by-side script uses
# the same three) — asserted explicitly so a shrunken oracle can't
# silently weaken this gate.
BIG_MONTHS = ("2026-06", "2026-03", "2026-01")


@pytest.fixture(scope="module")
def oracle() -> dict:
    if not ORACLE.exists():
        pytest.fail(f"oracle fixture missing: {ORACLE}")
    return json.loads(ORACLE.read_text(encoding="utf-8"))


def month_window(month_key: str) -> MonthWindow:
    year, month = month_key.split("-")
    return MonthWindow(int(year), int(month))


def month_filters(month_key: str) -> EntryFilters:
    w = month_window(month_key)
    return EntryFilters(start=w.start, end=w.end, status="all")


def test_oracle_covers_the_big_months(oracle):
    for month in BIG_MONTHS:
        assert month in oracle["months"], f"oracle lost {month}"


def test_t_rpt_project_rollup_equivalence(oracle, real_session, real_rian):
    """project_summary over each oracle month == the oracle groups,
    field by field, cent by cent."""
    mismatches = []
    for month, groups in sorted(oracle["months"].items()):
        result = project_summary(real_session, real_rian, month_filters(month))
        got = {}
        for row in result["rows"]:
            key = row["project_id"] or "unassigned"
            got[key] = row
        for key, expected in groups.items():
            row = got.get(key)
            if row is None:
                mismatches.append(f"{month}/{key}: group missing")
                continue
            checks = {
                "entry_count": (row["row_count"], expected["entry_count"]),
                "source_seconds": (row["seconds"], expected["source_seconds"]),
                "cost": (row["cost"]["amount"], expected["cost"]["CAD"]),
                "billout": (row["billout"]["amount"], expected["billout"]["CAD"]),
                "billout_adjusted": (
                    row["billout_adjusted"]["amount"],
                    expected["billout_adjusted"]["CAD"],
                ),
            }
            for field, (ours, oracle_value) in checks.items():
                if str(ours) != str(oracle_value):
                    mismatches.append(
                        f"{month}/{expected['project_name']}/{field}:"
                        f" ours={ours} oracle={oracle_value}"
                    )
        extra = set(got) - set(groups)
        if extra:
            mismatches.append(f"{month}: extra groups {sorted(extra)}")
    assert not mismatches, "\n".join(mismatches)


def test_t_rpt_totals_equivalence(oracle, real_session, real_rian):
    """entries_filter_totals over each month == Σ oracle groups."""
    for month, groups in sorted(oracle["months"].items()):
        totals = entry_totals(real_session, real_rian, month_filters(month))
        assert totals["row_count"] == sum(g["entry_count"] for g in groups.values()), month
        assert totals["source_seconds"] == sum(
            g["source_seconds"] for g in groups.values()
        ), month
        assert Decimal(totals["cost"]["amount"]) == sum(
            Decimal(g["cost"]["CAD"]) for g in groups.values()
        ), month
        assert Decimal(totals["billout"]["amount"]) == sum(
            Decimal(g["billout"]["CAD"]) for g in groups.values()
        ), month


def test_t_rpt_summary_window_equivalence(oracle, real_session, real_rian):
    """summary_by_project with this=<month>: per-project this_* values
    match the oracle month groups (the one NULL-duration row in the
    real data carries no stamps, so B14's exclusion changes nothing)."""
    for month in BIG_MONTHS:
        groups = oracle["months"][month]
        result = summary_by_project(real_session, real_rian, month_window(month))
        rows = {r["project_id"] or "unassigned": r for r in result["rows"]}
        for key, expected in groups.items():
            row = rows.get(key)
            assert row is not None, f"{month}/{key} missing from summary"
            assert row["this_seconds"] == expected["source_seconds"], (month, key)
            assert row["this_cost"]["amount"] == expected["cost"]["CAD"], (month, key)
            assert row["this_billout"]["amount"] == expected["billout"]["CAD"], (
                month, key,
            )
            assert (
                row["this_billout_adjusted"]["amount"]
                == expected["billout_adjusted"]["CAD"]
            ), (month, key)
        # projects absent from the oracle month contributed nothing
        for key, row in rows.items():
            if key not in groups:
                assert row["this_seconds"] == 0, (month, key)


def test_status_partition_on_real_data(real_session, real_rian):
    """pending + blocked + applied == all, over all 4,593 real rows."""
    counts = {
        status: entry_totals(
            real_session, real_rian, EntryFilters(status=status)
        )["row_count"]
        for status in ("all", "pending", "blocked", "applied")
    }
    assert counts["all"] >= 4593  # grows as the old app takes new entries
    assert counts["pending"] + counts["blocked"] + counts["applied"] == counts["all"]
    transferred = entry_totals(
        real_session, real_rian, EntryFilters(status="transferred")
    )["row_count"]
    assert transferred == counts["applied"]


def test_perf_note_hot_endpoints_on_full_dataset(real_session, real_rian):
    """The M3 perf sanity floor (the real gate is the orchestrator's):
    totals/summary/rollup/list each finish well inside the budget on
    the full dataset."""
    import time

    budget_ms = 500
    timings = {}
    t0 = time.perf_counter()
    entry_totals(real_session, real_rian, EntryFilters(status="pending"))
    timings["entries_totals"] = (time.perf_counter() - t0) * 1000

    t0 = time.perf_counter()
    summary_by_project(real_session, real_rian, MonthWindow(2026, 6))
    timings["summary"] = (time.perf_counter() - t0) * 1000

    t0 = time.perf_counter()
    project_summary(real_session, real_rian, EntryFilters(status="all"))
    timings["projects"] = (time.perf_counter() - t0) * 1000

    from app.services.reporting.entries import list_entries

    t0 = time.perf_counter()
    list_entries(real_session, real_rian, EntryFilters(status="pending"))
    timings["entries_page"] = (time.perf_counter() - t0) * 1000

    slow = {k: v for k, v in timings.items() if v > budget_ms}
    assert not slow, f"over the {budget_ms}ms floor: {slow} (all: {timings})"


def test_datetime_boundary_sanity():
    """Guard the month_filters helper itself."""
    f = month_filters("2026-12")
    assert f.start == datetime(2026, 12, 1) and f.end == datetime(2027, 1, 1)
