"""Integration tests for PIM tool functions.

Tests the tool layer end-to-end using tmp_path with test data.
No LLM calls needed — just verifies tools return formatted strings.
"""

from __future__ import annotations

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

import pytest
from icarus.pim.calendar import create_event
from icarus.pim.contacts import create_contact
from icarus.pim.todos import create_todo
from icarus.runtime.context import UserContext
from icarus.tools.pim import create_pim_tools


@pytest.fixture
def pim_ctx(tmp_path: Path) -> UserContext:
    """Create a UserContext with PIM directories populated with test data."""
    config_dir = tmp_path / ".icarus"
    config_dir.mkdir()

    ctx = UserContext(
        username="test",
        home_dir=tmp_path,
        working_dir=tmp_path,
        config_dir=config_dir,
    )

    # Create PIM directories
    ctx.contacts_dir.mkdir(parents=True, exist_ok=True)
    ctx.calendar_dir.mkdir(parents=True, exist_ok=True)
    ctx.todos_dir.mkdir(parents=True, exist_ok=True)

    # Seed contacts
    create_contact(ctx.contacts_dir, {
        "fn": "Alice Testuser",
        "email": ["alice@example.com"],
        "org": "ACME GmbH",
    })
    create_contact(ctx.contacts_dir, {
        "fn": "John Smith",
        "email": ["john@test.com"],
    })

    # Seed calendar events
    now = datetime.now(tz=timezone.utc)
    for i in range(3):
        create_event(ctx.calendar_dir, {
            "summary": f"Meeting {i+1}",
            "dtstart": now + timedelta(days=i, hours=10),
            "uid": f"event-{i+1}",
        })

    # Seed todos
    create_todo(ctx.todos_dir, {
        "summary": "Review PR",
        "priority": "high",
        "due": (now + timedelta(days=2)).isoformat(),
        "uid": "todo-1",
    })
    create_todo(ctx.todos_dir, {
        "summary": "Write docs",
        "priority": "low",
        "uid": "todo-2",
    })

    return ctx


@pytest.fixture
def tools(pim_ctx: UserContext) -> dict:
    """Create PIM tools and return them as a name → function dict."""
    tool_list = create_pim_tools(pim_ctx)
    return {t.__name__: t for t in tool_list}


class TestSearchContacts:
    def test_finds_by_name(self, tools: dict):
        result = tools["search_contacts"]("alice")
        assert "Alice Testuser" in result
        assert "alice@example.com" in result

    def test_list_all(self, tools: dict):
        result = tools["search_contacts"]("")
        assert "Alice Testuser" in result
        assert "John Smith" in result

    def test_no_match(self, tools: dict):
        result = tools["search_contacts"]("zzz")
        assert "No contacts found" in result


class TestGetContactDetails:
    def test_returns_details(self, tools: dict):
        result = tools["get_contact_details"]("alice")
        assert "Alice Testuser" in result
        assert "alice@example.com" in result
        assert "ACME GmbH" in result

    def test_not_found(self, tools: dict):
        result = tools["get_contact_details"]("nobody")
        assert "No contact found" in result


class TestSaveContact:
    def test_create_new(self, tools: dict, pim_ctx: UserContext):
        result = tools["save_contact"]("New Person", email="new@test.com")
        assert "Created contact" in result
        assert (pim_ctx.contacts_dir / "new-person.vcf").exists()

    def test_update_existing(self, tools: dict):
        result = tools["save_contact"]("Alice Testuser", email="newalice@test.com")
        assert "Updated contact" in result


class TestListUpcomingEvents:
    def test_lists_events(self, tools: dict):
        result = tools["list_upcoming_events"](7)
        assert "Meeting 1" in result
        assert "event(s)" in result

    def test_custom_days(self, tools: dict):
        result = tools["list_upcoming_events"](1)
        assert "event(s)" in result or "No events" in result


class TestCreateEvent:
    def test_creates_event(self, tools: dict, pim_ctx: UserContext):
        start = (datetime.now(tz=timezone.utc) + timedelta(days=5)).isoformat()
        result = tools["create_event"]("New Event", start)
        assert "Created event" in result

    def test_rejects_past_date(self, tools: dict):
        result = tools["create_event"]("Old Event", "2024-01-15T10:00")
        assert "past" in result.lower()
        assert "2024-01-15" in result

    def test_rejects_invalid_date(self, tools: dict):
        result = tools["create_event"]("Bad Event", "next tuesday")
        assert "Invalid date format" in result

    def test_rejects_end_before_start(self, tools: dict):
        start = (datetime.now(tz=timezone.utc) + timedelta(days=5, hours=10)).isoformat()
        end = (datetime.now(tz=timezone.utc) + timedelta(days=5, hours=9)).isoformat()
        result = tools["create_event"]("Bad Duration", start, end=end)
        assert "must be after start" in result

    def test_rejects_invalid_end(self, tools: dict):
        start = (datetime.now(tz=timezone.utc) + timedelta(days=5)).isoformat()
        result = tools["create_event"]("Bad End", start, end="not a date")
        assert "Invalid date format" in result


class TestAddTodo:
    def test_creates_todo(self, tools: dict, pim_ctx: UserContext):
        result = tools["add_todo"]("New Task", priority="medium")
        assert "Created task" in result

    def test_with_due_date(self, tools: dict):
        due = (datetime.now(tz=timezone.utc) + timedelta(days=7)).isoformat()
        result = tools["add_todo"]("Dated Task", due=due, priority="high")
        assert "Created task" in result

    def test_rejects_past_due_date(self, tools: dict):
        result = tools["add_todo"]("Old Task", due="2024-06-01")
        assert "past" in result.lower()

    def test_rejects_invalid_due_date(self, tools: dict):
        result = tools["add_todo"]("Bad Task", due="sometime soon")
        assert "Invalid date format" in result


class TestListTodos:
    def test_lists_open_with_urgency(self, tools: dict):
        result = tools["list_todos"]("open")
        assert "Review PR" in result
        assert "Write docs" in result
        # Urgency scores should be present (format: [X.Y])
        assert "[" in result

    def test_sort_by_urgency(self, tools: dict):
        result = tools["list_todos"]("open", "urgency")
        lines = result.strip().split("\n")
        # Review PR (high priority, has due date) should be listed first
        review_idx = next(i for i, line in enumerate(lines) if "Review PR" in line)
        docs_idx = next(i for i, line in enumerate(lines) if "Write docs" in line)
        assert review_idx < docs_idx


class TestCompleteTodo:
    def test_complete_by_summary(self, tools: dict):
        result = tools["complete_todo"]("Review PR")
        assert "Completed task" in result

    def test_no_match(self, tools: dict):
        result = tools["complete_todo"]("nonexistent task")
        assert "No open task found" in result

    def test_ambiguous_match(self, tools: dict):
        """When multiple tasks match, should ask for clarification."""
        # Both "Review PR" and "Write docs" contain common substrings
        # but let's add two similar tasks first
        tools["add_todo"]("Buy milk", priority="low")
        tools["add_todo"]("Buy bread", priority="low")
        result = tools["complete_todo"]("Buy")
        assert "Multiple tasks match" in result or "Completed task" in result

    def test_rejects_path_traversal(self, tools: dict):
        """Path traversal in summary_or_id must not escape todos dir."""
        result = tools["complete_todo"]("../../calendar/event-1")
        assert "No open task found" in result


class TestAddTodoEdgeCases:
    def test_empty_tags_filtered(self, tools: dict, pim_ctx: UserContext):
        """Trailing comma in tags should not produce empty strings."""
        from icarus.pim.todos import list_todos

        tools["add_todo"]("Tagged task", tags="work, ")
        todos = list_todos(pim_ctx.todos_dir, status="open")
        match = [t for t in todos if t["summary"] == "Tagged task"]
        assert match
        assert "" not in match[0].get("tags", [])


class TestSaveContactDuplicates:
    def test_updates_suffixed_duplicate(self, tools: dict, pim_ctx: UserContext):
        """save_contact should find and update john-smith-2.vcf, not create a third."""
        # Seed a second "John Smith" so contacts are john-smith.vcf & john-smith-2.vcf
        create_contact(pim_ctx.contacts_dir, {"fn": "John Smith", "email": ["john2@test.com"]})
        result = tools["save_contact"]("John Smith", email="updated@test.com")
        assert "Updated contact" in result
        # Should not have created a third John Smith
        smiths = list(pim_ctx.contacts_dir.glob("john-smith*.vcf"))
        assert len(smiths) == 2
