"""caldav_client.merge_tasks -- pure-function coverage (sort, per-source
color/owner tagging, partial-failure handling), monkeypatching
fetch_tasks itself rather than the caldav package's DAVClient/Calendar
-- this project has no CalDAV test server fixture (fetch_calendar_events'
own caldav branch is likewise only exercised this way, never against a
real server -- see test_calendar_feed.py, which only covers the ICS
path). fetch_tasks' own VTODO-parsing internals are trusted to the
icalendar library, same posture calendar_feed.py already takes for
VEVENT parsing."""

from __future__ import annotations

from datetime import datetime, timezone

from app.caldav_client import CalDavError, TaskSource, merge_tasks


def test_single_source_tasks_pass_through_tagged(monkeypatch):
    monkeypatch.setattr(
        "app.caldav_client.fetch_tasks",
        lambda url, username, password, completed_since=None: [
            {"summary": "Buy milk", "due": "2026-08-01", "completed_at": None},
        ],
    )
    sources = [TaskSource("Alice", "https://example.com/tasks", "alice", "pw", color_index=3)]
    tasks, summary = merge_tasks(sources)
    assert summary == ""
    assert tasks == [
        {"summary": "Buy milk", "due": "2026-08-01", "completed_at": None,
         "owner_display_name": "Alice", "color_index": 3},
    ]


def test_outstanding_tasks_sort_by_due_date_none_last(monkeypatch):
    def fake_fetch(url, username, password, completed_since=None):
        by_url = {
            "a": [{"summary": "No due date", "due": None, "completed_at": None}],
            "b": [{"summary": "Due soonest", "due": "2026-08-01", "completed_at": None}],
        }
        return by_url[url]

    monkeypatch.setattr("app.caldav_client.fetch_tasks", fake_fetch)
    sources = [TaskSource("Alice", "a", "alice", "pw"), TaskSource("Bob", "b", "bob", "pw")]
    tasks, _ = merge_tasks(sources)
    assert [t["summary"] for t in tasks] == ["Due soonest", "No due date"]


def test_completed_tasks_sort_after_outstanding_most_recent_first(monkeypatch):
    def fake_fetch(url, username, password, completed_since=None):
        return [
            {"summary": "Outstanding", "due": "2026-08-05", "completed_at": None},
            {"summary": "Done yesterday", "due": None, "completed_at": "2026-08-01T09:00:00+00:00"},
            {"summary": "Done today", "due": None, "completed_at": "2026-08-02T09:00:00+00:00"},
        ]

    monkeypatch.setattr("app.caldav_client.fetch_tasks", fake_fetch)
    tasks, _ = merge_tasks([TaskSource("Alice", "a", "alice", "pw")])
    assert [t["summary"] for t in tasks] == ["Outstanding", "Done today", "Done yesterday"]


def test_one_broken_source_does_not_blank_others(monkeypatch):
    def fake_fetch(url, username, password, completed_since=None):
        if url == "broken":
            raise CalDavError("nope")
        return [{"summary": "Buy milk", "due": None, "completed_at": None}]

    monkeypatch.setattr("app.caldav_client.fetch_tasks", fake_fetch)
    sources = [TaskSource("Alice", "ok", "alice", "pw"), TaskSource("Bob", "broken", "bob", "pw")]
    tasks, summary = merge_tasks(sources)
    assert len(tasks) == 1
    assert summary == "1 of 2 task lists unavailable"


def test_all_sources_unreachable(monkeypatch):
    monkeypatch.setattr(
        "app.caldav_client.fetch_tasks",
        lambda url, username, password, completed_since=None: (_ for _ in ()).throw(CalDavError("nope")),
    )
    sources = [TaskSource("Alice", "a", "alice", "pw"), TaskSource("Bob", "b", "bob", "pw")]
    tasks, summary = merge_tasks(sources)
    assert tasks == []
    assert summary == "2 of 2 task lists unavailable"


def test_completed_since_is_forwarded_to_fetch_tasks(monkeypatch):
    seen = []

    def fake_fetch(url, username, password, completed_since=None):
        seen.append(completed_since)
        return []

    monkeypatch.setattr("app.caldav_client.fetch_tasks", fake_fetch)
    cutoff = datetime(2026, 8, 1, tzinfo=timezone.utc)
    merge_tasks([TaskSource("Alice", "a", "alice", "pw")], completed_since=cutoff)
    assert seen == [cutoff]
