from __future__ import annotations

from datetime import date, time, timedelta
from pathlib import Path

from checkin_cli.weekly_operations_summary import build_weekly_operations_summary
from checkin_cli.models import ContractCheckin, EventType
from checkin_cli.weekly_operations import DayState
from tests._weekly_operations_correlation_support import event
from tests._weekly_operations_summary_support import (
    KST,
    SyntheticStatus,
    append_status,
    approved_synthetic_week,
    fixture_for_events,
)


def test_summary_keeps_sunday_235959_and_monday_000000_in_their_calendar_weeks(
    tmp_path: Path,
) -> None:
    # Given: a late Sunday response and a new Monday response on exact KST boundaries.
    sunday = event("summary-sunday-late", occurred_at="2026-08-23T23:59:59+09:00")
    monday = event("summary-monday-start", occurred_at="2026-08-24T00:00:00+09:00")
    fixture = fixture_for_events(tmp_path, (sunday, monday))
    try:
        append_status(
            fixture,
            SyntheticStatus(date(2026, 8, 23), DayState.MISSED, 0, occurred_time=time(23)),
        )
        append_status(
            fixture,
            SyntheticStatus(
                date(2026, 8, 23),
                DayState.LATE_SUBMITTED,
                1,
                sunday.event_id,
                occurred_time=time(23, 59, 59),
            ),
        )
        append_status(
            fixture,
            SyntheticStatus(
                date(2026, 8, 24),
                DayState.SUBMITTED,
                2,
                monday.event_id,
                occurred_time=time(),
            ),
        )

        # When: adjacent Monday-Sunday windows are built from the same authorities.
        sunday_week = build_weekly_operations_summary(
            fixture.canonical.source, fixture.sidecar.store, date(2026, 8, 17)
        )
        monday_week = build_weekly_operations_summary(
            fixture.canonical.source, fixture.sidecar.store, date(2026, 8, 24)
        )

        # Then: no timestamp reinterpretation moves either confirmed sidecar state.
        assert (sunday_week.submitted_count, sunday_week.late_count, sunday_week.missed_count) == (0, 1, 0)
        assert (monday_week.submitted_count, monday_week.late_count, monday_week.missed_count) == (1, 0, 0)
        assert sunday_week.window_end_kst.utcoffset() == timedelta(hours=9)
        assert monday_week.window_start_kst.tzinfo == KST
    finally:
        fixture.close()


def test_summary_counts_active_correction_once_and_duplicate_replay_never_adds_day(
    tmp_path: Path,
) -> None:
    # Given: the approved week has one replay and a later active correction lineage.
    fixture = approved_synthetic_week(tmp_path)
    try:
        duplicate = SyntheticStatus(
            date(2026, 8, 23), DayState.SUBMITTED, 8, "summary-current-sun"
        )
        rows_before = fixture.sidecar.store.read()
        append_status(fixture, duplicate)
        assert fixture.sidecar.store.read() == rows_before
        correction = event(
            "summary-current-mon-correction",
            EventType.CORRECTION,
            occurred_at="2026-08-24T10:00:00+09:00",
            supersedes="summary-current-mon",
            check_in=ContractCheckin(body_weight_kg=90.0),
        )
        _ = fixture.canonical.transaction.append_one(correction)
        append_status(
            fixture,
            SyntheticStatus(
                date(2026, 8, 17),
                DayState.SUBMITTED,
                9,
                correction.event_id,
            ),
        )

        # When: the corrected week is aggregated.
        summary = build_weekly_operations_summary(
            fixture.canonical.source, fixture.sidecar.store, date(2026, 8, 17)
        )

        # Then: one active lineage represents one completed day and no raw correction value leaks.
        assert (summary.submitted_count, summary.late_count, summary.missed_count) == (4, 1, 2)
        assert (summary.completed_days, summary.calendar_days) == (5, 7)
        assert "90.0" not in summary.to_json()
    finally:
        fixture.close()
