"""Юниты общего слоя core: календарь года и пагинация AlfaCRM (без сети).

Главный регресс, который тут закрыт: «текущий месяц» раньше определялся по первой
попавшейся операции Финтабло, и ПЛАНОВЫЕ операции следующего месяца уводили весь
расчёт на месяц вперёд (25 июля система считала текущим месяцем август, июль выпадал
из прогноза кассы). Теперь месяц берётся из календарной даты.
"""
import datetime
import pytest
import core


# ── календарь ──

def test_month_key():
    assert core.month_key(datetime.date(2026, 7, 25)) == "07.2026"
    assert core.month_key(datetime.date(2026, 12, 1)) == "12.2026"


def test_next_month_key_rolls_year():
    assert core.next_month_key(datetime.date(2026, 7, 25)) == "08.2026"
    assert core.next_month_key(datetime.date(2026, 12, 31)) == "01.2027"


def test_months_to_now_stops_at_current_month():
    ms = core.months_to_now(datetime.date(2026, 7, 25), year=2026)
    assert ms[-1] == "07.2026" and len(ms) == 7


def test_months_to_now_ignores_future_plan_months():
    """25 июля текущий месяц — июль, даже если в базе висит план на 31.08 (исходный баг)."""
    assert "08.2026" not in core.months_to_now(datetime.date(2026, 7, 25), year=2026)


def test_months_to_now_january_gives_one_month():
    assert core.months_to_now(datetime.date(2027, 1, 3), year=2027) == ["01.2027"]


def test_months_to_now_past_year_is_full():
    assert len(core.months_to_now(datetime.date(2027, 3, 1), year=2026)) == 12


def test_year_range():
    assert core.year_range(2027) == ("01.01.2027", "31.12.2027")


def test_month_range_alfa_uses_dashes():
    # с точками AlfaCRM молча игнорирует фильтр и отдаёт всю историю
    assert core.month_range_alfa("02.2026") == ("2026-02-01", "2026-02-28")
    assert core.month_range_alfa("07.2026") == ("2026-07-01", "2026-07-31")


# ── пагинация AlfaCRM: `count` — размер страницы, конец определяем по `total` ──

def _fake_alfa(pages):
    calls = []
    def fake(path, body, token):
        calls.append(body["page"])
        return pages[body["page"]] if body["page"] < len(pages) else {"items": [], "total": 0}
    fake.calls = calls
    return fake


def test_alfa_all_collects_until_total(monkeypatch):
    pages = [{"items": [{"id": i} for i in range(500)], "count": 500, "total": 700},
             {"items": [{"id": i} for i in range(500, 700)], "count": 200, "total": 700}]
    fake = _fake_alfa(pages)
    monkeypatch.setattr(core, "alfa", fake)
    out = core.alfa_all("/v2api/3/pay/index", {"pay_type_id": 1}, "tok")
    assert len(out) == 700
    assert fake.calls == [0, 1]          # без лишнего пустого запроса


def test_alfa_all_stops_on_empty_page(monkeypatch):
    pages = [{"items": [{"id": 1}], "count": 1}, {"items": [], "count": 0}]
    monkeypatch.setattr(core, "alfa", _fake_alfa(pages))
    assert len(core.alfa_all("/x", {}, "tok")) == 1


def test_alfa_all_raises_instead_of_silent_truncation(monkeypatch):
    # бесконечная выдача: упереться в потолок молча = тихая потеря данных
    monkeypatch.setattr(core, "alfa",
                        lambda p, b, t: {"items": [{"id": b["page"]}], "count": 1, "total": 10 ** 9})
    monkeypatch.setattr(core, "ALFA_MAX_PAGES", 3)
    with pytest.raises(RuntimeError):
        core.alfa_all("/x", {}, "tok")


def test_alfa_raises_on_http_error(monkeypatch):
    import io, urllib.error
    def boom(req, timeout=None):
        raise urllib.error.HTTPError('http://x', 403, 'forbidden', {}, io.BytesIO(b''))
    monkeypatch.setattr(core.urllib.request, "urlopen", boom)
    with pytest.raises(RuntimeError):
        core.alfa("/x", {}, "tok")


# ── форматирование ──

def test_money_and_num():
    assert core.money(1234567) == "1 234 567"
    assert core.num("1 234,50 ₽") == 1234.5
    assert core.num("") == 0.0
