from __future__ import annotations

import importlib.util
import sys
from pathlib import Path

import pytest


ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "scripts/check_ym_acknowledged_losses.py"
SPEC = importlib.util.spec_from_file_location("check_ym_acknowledged_losses", SCRIPT)
assert SPEC is not None and SPEC.loader is not None
MODULE = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = MODULE
SPEC.loader.exec_module(MODULE)


def _materialized_row(entry: dict[str, object]) -> dict[str, object]:
    return {
        "mp_id": entry["mpId"],
        "return_id": entry["returnId"],
        "order_id": entry["orderId"],
        "line_no": entry["lineNo"],
        "shop_sku": entry["shopSku"],
        "item_count": entry["itemCount"],
        "model_id": entry["modelId"],
    }


class _Cursor:
    """Заглушка отвечает на два запроса: число payload'ов и сами item-строки."""

    def __init__(
        self, rows: list[dict[str, object]], payload_count: int = 129
    ) -> None:
        self.rows = rows
        self.payload_count = payload_count
        self.queries: list[str] = []
        self.params: tuple[object, ...] | None = None

    def __enter__(self) -> _Cursor:
        return self

    def __exit__(self, *_args: object) -> bool:
        return False

    def execute(self, query: str, params: tuple[object, ...]) -> None:
        assert query.lstrip().upper().startswith("SELECT")
        self.queries.append(query)
        self.params = params

    def fetchone(self) -> dict[str, object]:
        return {"payload_count": self.payload_count}

    def fetchall(self) -> list[dict[str, object]]:
        return self.rows

    @property
    def materialization_query(self) -> str:
        return self.queries[-1]


class _Connection:
    def __init__(
        self, rows: list[dict[str, object]], payload_count: int = 129
    ) -> None:
        self.cursor_value = _Cursor(rows, payload_count)
        self.closed = False

    def cursor(self) -> _Cursor:
        return self.cursor_value

    def close(self) -> None:
        self.closed = True


def _all_rows() -> list[dict[str, object]]:
    return [
        _materialized_row(entry)
        for entry in MODULE.ym_acknowledged_loss_check.ACKNOWLEDGED_YM_LOSS_ENTRIES
    ]


def test_all_79_acknowledged_entries_are_green_on_substituted_data(
    monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    connection = _Connection(_all_rows())
    monkeypatch.setattr(MODULE, "open_database_connection", lambda: connection)

    assert MODULE.main([]) == 0

    output = capsys.readouterr().out
    assert "все 79 заявленных потерь BR-009 найдены" in output
    assert connection.closed is True
    assert connection.cursor_value.queries
    assert 0 < len(connection.cursor_value.params or ()) <= 79
    assert "ROW_NUMBER() OVER" in connection.cursor_value.materialization_query
    assert "JSON_TABLE" in connection.cursor_value.materialization_query


def test_missing_acknowledged_entry_is_red_and_names_the_coordinate(
    monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    rows = _all_rows()
    missing = MODULE.ym_acknowledged_loss_check.ACKNOWLEDGED_YM_LOSS_ENTRIES[0]
    connection = _Connection(rows[1:])
    monkeypatch.setattr(MODULE, "open_database_connection", lambda: connection)

    assert MODULE.main([]) == 1

    output = capsys.readouterr().out
    assert "не материализованы 1 из 79" in output
    assert f"legacyReturnId={missing['legacyReturnId']}" in output
    assert f"returnId={missing['returnId']}" in output


def test_source_not_ingested_is_not_a_regression(
    monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    """Ноль payload'ов источника — сверять нечего, а не «всё пропало»."""
    connection = _Connection(_all_rows(), payload_count=0)
    monkeypatch.setattr(MODULE, "open_database_connection", lambda: connection)

    assert MODULE.main([]) == 0

    output = capsys.readouterr().out
    assert "ПРОПУЩЕНО" in output
    assert MODULE.ym_acknowledged_loss_check.LEDGER_SOURCE_ENDPOINT in output
    # Пропуск не должен выглядеть как успешная сверка.
    assert "найдены" not in output
    # Дорогой запрос по item-строкам при пустом источнике не выполняется.
    assert len(connection.cursor_value.queries) == 1
    assert "JSON_TABLE" not in connection.cursor_value.queries[0]


def test_source_endpoint_comes_from_reviewed_contract() -> None:
    """Строка эндпоинта не дублируется в проверке, а читается из контракта."""
    assert (
        MODULE.ym_acknowledged_loss_check.LEDGER_SOURCE_ENDPOINT
        == "ym.returns.legacy_recovery"
    )
