from __future__ import annotations

from decimal import Decimal
from datetime import date
from pathlib import Path

import checkin_cli.weekly_operations_summary as weekly_summary
import pytest
from checkin_cli.models import ContractCheckin
from checkin_cli.store import CanonicalEventSnapshot
from checkin_cli.weekly_operations import DayState, WeeklyOperationsConflict
from checkin_cli.weekly_operations_lineage import (
    LineageResolution,
    SelectedLineage,
    resolve_canonical_lineage,
)
from checkin_cli.weekly_operations_summary_types import WeightTrend
from tests._weekly_operations_correlation_support import event
from tests._weekly_operations_summary_support import (
    SyntheticStatus,
    append_status,
    fixture_for_events,
)


def _summary_for_weights(
    tmp_path: Path, first: float | None, last: float | None
) -> WeightTrend:
    first_event = event(
        "summary-weight-first",
        occurred_at="2026-08-17T08:00:00+09:00",
        check_in=ContractCheckin(body_weight_kg=first),
    )
    last_event = event(
        "summary-weight-last",
        occurred_at="2026-08-18T08:00:00+09:00",
        check_in=ContractCheckin(body_weight_kg=last),
    )
    fixture = fixture_for_events(tmp_path, (first_event, last_event))
    try:
        append_status(
            fixture,
            SyntheticStatus(
                date(2026, 8, 17), DayState.SUBMITTED, 1, first_event.event_id
            ),
        )
        append_status(
            fixture,
            SyntheticStatus(
                date(2026, 8, 18), DayState.SUBMITTED, 2, last_event.event_id
            ),
        )
        return weekly_summary.build_weekly_operations_summary(
            fixture.canonical.source, fixture.sidecar.store, date(2026, 8, 17)
        ).weight_trend
    finally:
        fixture.close()


@pytest.mark.parametrize(
    ("first", "last", "expected"),
    (
        (80.0, 80.29999999999999, WeightTrend.STABLE),
        (80.0, 79.70000000000002, WeightTrend.STABLE),
        (80.0, 80.3, WeightTrend.INCREASING),
        (80.0, 79.7, WeightTrend.DECREASING),
        (80.0, 80.30000000000001, WeightTrend.INCREASING),
        (80.0, 79.69999999999999, WeightTrend.DECREASING),
        (80.0, 80.0, WeightTrend.STABLE),
        (80.10, 80.40, WeightTrend.INCREASING),
        (499.999, 499.699, WeightTrend.DECREASING),
    ),
)
def test_weight_trend_uses_exact_decimal_thresholds(
    tmp_path: Path, first: float, last: float, expected: WeightTrend
) -> None:
    assert _summary_for_weights(tmp_path, first, last) is expected


@pytest.mark.parametrize(
    ("first", "last"),
    ((80.0, None), (None, 80.0), (None, None)),
)
def test_weight_trend_requires_two_present_values(
    tmp_path: Path, first: float | None, last: float | None
) -> None:
    assert _summary_for_weights(tmp_path, first, last) is WeightTrend.INSUFFICIENT_DATA


@pytest.mark.parametrize(
    "invalid_weight",
    (0.0, float("nan"), float("inf"), -float("inf"), "not-a-number", Decimal("80")),
)
def test_weight_trend_fails_closed_for_invalid_canonical_weight_values(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
    invalid_weight: float | str | Decimal,
) -> None:
    root = event(
        "summary-invalid-weight",
        occurred_at="2026-08-17T08:00:00+09:00",
        check_in=ContractCheckin(body_weight_kg=80.0),
    )
    fixture = fixture_for_events(tmp_path, (root,))
    original = resolve_canonical_lineage

    def resolve_invalid_weight(
        snapshot: CanonicalEventSnapshot, day: date
    ) -> LineageResolution:
        resolution = original(snapshot, day)
        if isinstance(resolution, SelectedLineage):
            check_in = resolution.source.check_in
            assert check_in is not None
            object.__setattr__(check_in, "body_weight_kg", invalid_weight)
        return resolution

    try:
        append_status(
            fixture,
            SyntheticStatus(date(2026, 8, 17), DayState.SUBMITTED, 1, root.event_id),
        )
        monkeypatch.setattr(weekly_summary, "resolve_canonical_lineage", resolve_invalid_weight)
        with pytest.raises(WeeklyOperationsConflict, match="body weight is invalid") as raised:
            _ = weekly_summary.build_weekly_operations_summary(
                fixture.canonical.source, fixture.sidecar.store, date(2026, 8, 17)
            )
        assert "not-a-number" not in str(raised.value)
    finally:
        fixture.close()
