"""Tests for PIM todos data layer."""

from __future__ import annotations

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

import pytest
from icarus.pim.todos import (
    complete_todo,
    compute_urgency,
    create_todo,
    list_todos,
    parse_todo,
)


class TestCreateAndParseTodo:
    def test_roundtrip(self, tmp_path: Path):
        data = {
            "summary": "Review PR",
            "priority": "high",
            "due": datetime(2025, 3, 20, 17, 0, tzinfo=timezone.utc),
            "project": "icarus",
            "tags": ["code-review", "urgent"],
            "description": "Review the PIM implementation PR",
        }
        path = create_todo(tmp_path, data)
        assert path.exists()

        parsed = parse_todo(path)
        assert parsed["summary"] == "Review PR"
        assert parsed["priority"] == "high"
        assert parsed["project"] == "icarus"
        assert parsed["status"] == "NEEDS-ACTION"

    def test_string_due_date(self, tmp_path: Path):
        data = {"summary": "String due", "due": "2025-03-20T17:00:00+00:00"}
        path = create_todo(tmp_path, data)
        parsed = parse_todo(path)
        assert parsed["due"] is not None

    def test_default_status(self, tmp_path: Path):
        path = create_todo(tmp_path, {"summary": "New task"})
        parsed = parse_todo(path)
        assert parsed["status"] == "NEEDS-ACTION"

    def test_creates_dir_if_missing(self, tmp_path: Path):
        d = tmp_path / "new_todos"
        path = create_todo(d, {"summary": "Auto"})
        assert path.exists()
        assert d.exists()


class TestListTodos:
    @pytest.fixture
    def todos_dir(self, tmp_path: Path) -> Path:
        d = tmp_path / "todos"
        d.mkdir()
        create_todo(d, {"summary": "Open 1", "uid": "open-1"})
        create_todo(d, {"summary": "Open 2", "uid": "open-2", "priority": "high"})
        # Create a completed todo
        create_todo(d, {"summary": "Done 1", "uid": "done-1", "status": "COMPLETED"})
        return d

    def test_filter_open(self, todos_dir: Path):
        todos = list_todos(todos_dir, status="open")
        summaries = [t["summary"] for t in todos]
        assert "Open 1" in summaries
        assert "Open 2" in summaries
        assert "Done 1" not in summaries

    def test_filter_completed(self, todos_dir: Path):
        todos = list_todos(todos_dir, status="completed")
        summaries = [t["summary"] for t in todos]
        assert "Done 1" in summaries
        assert "Open 1" not in summaries

    def test_filter_all(self, todos_dir: Path):
        todos = list_todos(todos_dir, status="all")
        assert len(todos) == 3

    def test_nonexistent_dir(self, tmp_path: Path):
        todos = list_todos(tmp_path / "nope")
        assert todos == []


class TestComputeUrgency:
    def test_due_date_urgency(self):
        now = datetime(2025, 3, 15, 12, 0, tzinfo=timezone.utc)

        # Due tomorrow — high urgency
        todo_soon = {"due": now + timedelta(days=1), "priority_int": 0}
        score_soon = compute_urgency(todo_soon, now=now)

        # Due in 2 weeks — low urgency
        todo_far = {"due": now + timedelta(days=14), "priority_int": 0}
        score_far = compute_urgency(todo_far, now=now)

        assert score_soon > score_far

    def test_overdue_maxes_out(self):
        """Overdue items should get maximum due-date urgency."""
        now = datetime(2025, 3, 15, 12, 0, tzinfo=timezone.utc)
        todo = {"due": now - timedelta(days=3), "priority_int": 0}
        score = compute_urgency(todo, now=now)
        # Due component should be at max (4.0) when overdue
        assert score >= 4.0

    def test_priority_urgency(self):
        now = datetime(2025, 3, 15, 12, 0, tzinfo=timezone.utc)

        high = {"priority_int": 1}  # highest
        low = {"priority_int": 9}  # lowest
        none = {"priority_int": 0}  # undefined

        assert compute_urgency(high, now=now) > compute_urgency(low, now=now)
        assert compute_urgency(none, now=now) == 0.0

    def test_age_urgency(self):
        now = datetime(2025, 3, 15, 12, 0, tzinfo=timezone.utc)

        old = {"created": now - timedelta(days=60), "priority_int": 0}
        new = {"created": now - timedelta(days=1), "priority_int": 0}

        assert compute_urgency(old, now=now) > compute_urgency(new, now=now)

    def test_age_caps_at_30_days(self):
        """Age contribution should cap at 1.0 (30 days)."""
        now = datetime(2025, 3, 15, 12, 0, tzinfo=timezone.utc)

        day30 = {"created": now - timedelta(days=30), "priority_int": 0}
        day90 = {"created": now - timedelta(days=90), "priority_int": 0}

        # Both should have the same age contribution (capped at 1.0)
        assert compute_urgency(day30, now=now) == compute_urgency(day90, now=now)

    def test_blocked_penalty(self):
        now = datetime(2025, 3, 15, 12, 0, tzinfo=timezone.utc)
        todo = {"due": now + timedelta(days=3), "priority_int": 1, "blocked": False}
        blocked_todo = {"due": now + timedelta(days=3), "priority_int": 1, "blocked": True}

        score_normal = compute_urgency(todo, now=now)
        score_blocked = compute_urgency(blocked_todo, now=now)

        assert score_blocked == pytest.approx(score_normal * 0.5, abs=0.01)

    def test_no_fields(self):
        """Empty todo should have 0 urgency."""
        assert compute_urgency({}) == 0.0


class TestTodoLinking:
    def test_attendees_roundtrip(self, tmp_path: Path):
        """Related people should roundtrip through .ics."""
        data = {
            "summary": "Handle pricing clarification",
            "attendees": [
                {"name": "Alice Smith", "email": "alice@example.com"},
            ],
        }
        path = create_todo(tmp_path, data)
        parsed = parse_todo(path)
        assert len(parsed["attendees"]) == 1
        assert parsed["attendees"][0]["email"] == "alice@example.com"
        assert parsed["attendees"][0]["name"] == "Alice Smith"

    def test_assigned_to_roundtrip(self, tmp_path: Path):
        """Assigned person should roundtrip with ROLE=ASSIGNEE."""
        data = {
            "summary": "Deploy updates",
            "assigned_to": "Bob Jones <bob@example.com>",
        }
        path = create_todo(tmp_path, data)
        parsed = parse_todo(path)
        assert parsed["assigned_to"] == "Bob Jones"

    def test_related_event_roundtrip(self, tmp_path: Path):
        """RELATED-TO should link a todo to a calendar event UID."""
        event_uid = "meeting-2026-03-09"
        data = {
            "summary": "Prepare agenda",
            "related_to": [event_uid],
        }
        path = create_todo(tmp_path, data)
        parsed = parse_todo(path)
        assert event_uid in parsed["related_to"]

    def test_full_linking(self, tmp_path: Path):
        """All three linking types together."""
        data = {
            "summary": "Prepare pricing doc",
            "attendees": [
                {"name": "Alice Smith", "email": "alice@example.com"},
            ],
            "assigned_to": "Bob Jones <bob@example.com>",
            "related_to": ["event-uid-123"],
        }
        path = create_todo(tmp_path, data)
        parsed = parse_todo(path)
        assert len(parsed["attendees"]) == 1
        assert parsed["assigned_to"] == "Bob Jones"
        assert "event-uid-123" in parsed["related_to"]

    def test_no_linking_defaults_empty(self, tmp_path: Path):
        """Todos without linking should have empty defaults."""
        path = create_todo(tmp_path, {"summary": "Plain task"})
        parsed = parse_todo(path)
        assert parsed["attendees"] == []
        assert parsed["assigned_to"] == ""
        assert parsed["related_to"] == []

    def test_string_format_attendees(self, tmp_path: Path):
        """'Name <email>' string format should work for attendees."""
        data = {
            "summary": "Review with team",
            "attendees": ["Alice Smith <alice@example.com>"],
        }
        path = create_todo(tmp_path, data)
        parsed = parse_todo(path)
        assert len(parsed["attendees"]) == 1
        assert parsed["attendees"][0]["name"] == "Alice Smith"


class TestCompleteTodo:
    def test_completes_todo(self, tmp_path: Path):
        d = tmp_path / "todos"
        d.mkdir()
        create_todo(d, {"summary": "Complete me", "uid": "complete-test"})

        path = complete_todo(d, "complete-test")
        parsed = parse_todo(path)
        assert parsed["status"] == "COMPLETED"
        assert parsed["completed"] is not None

    def test_complete_already_completed_is_noop(self, tmp_path: Path):
        d = tmp_path / "todos"
        d.mkdir()
        create_todo(d, {"summary": "Already done", "uid": "noop-test"})
        complete_todo(d, "noop-test")
        ts1 = parse_todo(d / "noop-test.ics")["completed"]
        complete_todo(d, "noop-test")
        ts2 = parse_todo(d / "noop-test.ics")["completed"]
        assert ts1 == ts2

    def test_raises_on_missing(self, tmp_path: Path):
        d = tmp_path / "todos"
        d.mkdir()
        with pytest.raises(FileNotFoundError):
            complete_todo(d, "nonexistent")
