"""Tests for PIM calendar data layer."""

from __future__ import annotations

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

import pytest
from icarus.pim.calendar import (
    create_event,
    delete_event,
    list_events,
    parse_event,
    update_event,
)


class TestCreateAndParseEvent:
    def test_roundtrip(self, tmp_path: Path):
        data = {
            "summary": "Team Standup",
            "dtstart": datetime(2025, 3, 15, 10, 0, tzinfo=timezone.utc),
            "dtend": datetime(2025, 3, 15, 10, 30, tzinfo=timezone.utc),
            "location": "Meeting Room A",
            "description": "Daily standup",
        }
        path = create_event(tmp_path, data)
        assert path.exists()

        parsed = parse_event(path)
        assert parsed["summary"] == "Team Standup"
        assert parsed["location"] == "Meeting Room A"
        assert parsed["description"] == "Daily standup"
        assert parsed["dtstart"] is not None
        assert parsed["dtend"] is not None

    def test_default_end_time(self, tmp_path: Path):
        """End time defaults to start + 1 hour when not specified."""
        start = datetime(2025, 3, 15, 10, 0, tzinfo=timezone.utc)
        data = {"summary": "Quick meeting", "dtstart": start}
        path = create_event(tmp_path, data)

        parsed = parse_event(path)
        assert parsed["dtend"] is not None
        # Should be approximately 1 hour after start
        delta = parsed["dtend"] - parsed["dtstart"]
        assert delta == timedelta(hours=1)

    def test_string_datetime(self, tmp_path: Path):
        """ISO format strings should be accepted."""
        data = {
            "summary": "String dates",
            "dtstart": "2025-03-15T10:00:00+00:00",
            "dtend": "2025-03-15T11:00:00+00:00",
        }
        path = create_event(tmp_path, data)
        parsed = parse_event(path)
        assert parsed["summary"] == "String dates"
        assert parsed["dtstart"] is not None

    def test_creates_dir_if_missing(self, tmp_path: Path):
        d = tmp_path / "new_calendar"
        path = create_event(d, {"summary": "Auto", "dtstart": datetime.now(tz=timezone.utc)})
        assert path.exists()
        assert d.exists()


class TestListEvents:
    @pytest.fixture
    def calendar_dir(self, tmp_path: Path) -> Path:
        d = tmp_path / "calendar"
        d.mkdir()
        # Create events across a range
        for i in range(5):
            dt = datetime(2025, 3, 10 + i, 10, 0, tzinfo=timezone.utc)
            create_event(d, {"summary": f"Event {i+1}", "dtstart": dt})
        return d

    def test_filter_by_date_range(self, calendar_dir: Path):
        events = list_events(calendar_dir, date(2025, 3, 11), date(2025, 3, 13))
        summaries = [e["summary"] for e in events]
        assert "Event 2" in summaries
        assert "Event 3" in summaries
        assert "Event 4" in summaries
        # Event 1 (Mar 10) and Event 5 (Mar 14) should be excluded
        assert "Event 1" not in summaries
        assert "Event 5" not in summaries

    def test_empty_range(self, calendar_dir: Path):
        events = list_events(calendar_dir, date(2025, 1, 1), date(2025, 1, 2))
        assert len(events) == 0

    def test_sorted_by_start_time(self, calendar_dir: Path):
        events = list_events(calendar_dir, date(2025, 3, 10), date(2025, 3, 14))
        starts = [e["dtstart"] for e in events]
        assert starts == sorted(starts)

    def test_nonexistent_dir(self, tmp_path: Path):
        events = list_events(tmp_path / "nope", date(2025, 3, 1), date(2025, 3, 31))
        assert events == []


class TestRecurrenceExpansion:
    def test_weekly_recurrence(self, tmp_path: Path):
        """Recurring events should be expanded to concrete instances."""
        import icalendar

        # Create a weekly recurring event manually
        cal = icalendar.Calendar()
        cal.add("prodid", "-//Test//")
        cal.add("version", "2.0")
        event = icalendar.Event()
        event.add("uid", "recurring-test")
        event.add("summary", "Weekly Standup")
        event.add("dtstart", datetime(2025, 3, 3, 10, 0, tzinfo=timezone.utc))  # Monday
        event.add("dtend", datetime(2025, 3, 3, 10, 30, tzinfo=timezone.utc))
        event.add("rrule", {"freq": "weekly", "count": 4})
        event.add("dtstamp", datetime.now(tz=timezone.utc))
        cal.add_component(event)

        d = tmp_path / "calendar"
        d.mkdir()
        (d / "recurring-test.ics").write_bytes(cal.to_ical())

        # List events for 4 weeks — should see multiple instances
        events = list_events(d, date(2025, 3, 1), date(2025, 3, 31))
        assert len(events) >= 2  # At least 2 Monday instances in March


class TestUpdateEvent:
    def test_update_summary(self, tmp_path: Path):
        d = tmp_path / "calendar"
        d.mkdir()
        dt = datetime(2025, 3, 15, 10, 0, tzinfo=timezone.utc)
        path = create_event(d, {"summary": "Old Title", "dtstart": dt, "uid": "update-test"})

        update_event(d, "update-test", {"summary": "New Title"})
        parsed = parse_event(path)
        assert parsed["summary"] == "New Title"

    def test_raises_on_missing(self, tmp_path: Path):
        d = tmp_path / "calendar"
        d.mkdir()
        with pytest.raises(FileNotFoundError):
            update_event(d, "nonexistent-uid", {"summary": "nope"})


class TestAttendees:
    def test_create_with_attendees_dict(self, tmp_path: Path):
        """Attendees as list of dicts should roundtrip through .ics."""
        data = {
            "summary": "Team Lunch",
            "dtstart": datetime(2026, 3, 9, 18, 0, tzinfo=timezone.utc),
            "attendees": [
                {"name": "Alice Smith", "email": "alice@example.com"},
                {"name": "Bob Jones", "email": "bob@example.com"},
            ],
        }
        path = create_event(tmp_path, data)
        parsed = parse_event(path)
        assert len(parsed["attendees"]) == 2
        emails = {a["email"] for a in parsed["attendees"]}
        assert "alice@example.com" in emails
        assert "bob@example.com" in emails

    def test_create_with_attendees_string_format(self, tmp_path: Path):
        """Attendees as 'Name <email>' strings should work."""
        data = {
            "summary": "Coffee",
            "dtstart": datetime(2026, 3, 10, 10, 0, tzinfo=timezone.utc),
            "attendees": [
                "Alice Smith <alice@example.com>",
                "Bob Jones <bob@example.com>",
            ],
        }
        path = create_event(tmp_path, data)
        parsed = parse_event(path)
        assert len(parsed["attendees"]) == 2
        names = {a["name"] for a in parsed["attendees"]}
        assert "Alice Smith" in names

    def test_create_with_email_only_attendee(self, tmp_path: Path):
        """Just an email (no name) should also work."""
        data = {
            "summary": "Quick sync",
            "dtstart": datetime(2026, 3, 10, 14, 0, tzinfo=timezone.utc),
            "attendees": [{"name": "", "email": "someone@example.com"}],
        }
        path = create_event(tmp_path, data)
        parsed = parse_event(path)
        assert len(parsed["attendees"]) == 1
        assert parsed["attendees"][0]["email"] == "someone@example.com"

    def test_no_attendees_returns_empty_list(self, tmp_path: Path):
        """Events without attendees should have an empty attendees list."""
        data = {
            "summary": "Solo work",
            "dtstart": datetime(2026, 3, 10, 9, 0, tzinfo=timezone.utc),
        }
        path = create_event(tmp_path, data)
        parsed = parse_event(path)
        assert parsed["attendees"] == []


class TestDeleteEvent:
    def test_delete_existing(self, tmp_path: Path):
        d = tmp_path / "calendar"
        d.mkdir()
        create_event(d, {
            "summary": "Delete Me",
            "dtstart": datetime.now(tz=timezone.utc),
            "uid": "delete-test",
        })
        assert delete_event(d, "delete-test") is True
        assert not (d / "delete-test.ics").exists()

    def test_delete_nonexistent(self, tmp_path: Path):
        d = tmp_path / "calendar"
        d.mkdir()
        assert delete_event(d, "nonexistent") is False
