"""Tests for CalendarTool — Agent tool wrapper for ReAct integration (U3)."""

from __future__ import annotations

import asyncio
from pathlib import Path

import pytest

from agentkit.calendar.db import get_event_tags, init_calendar_db
from agentkit.calendar.service import CalendarService
from agentkit.tools.calendar_tool import CalendarTool


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def service(tmp_path: Path) -> CalendarService:
    """Provide a CalendarService backed by a temp DB."""
    db_path = tmp_path / "test.db"
    asyncio.run(init_calendar_db(db_path))
    return CalendarService(db_path=db_path)


@pytest.fixture
def tool(service: CalendarService) -> CalendarTool:
    return CalendarTool(calendar_service=service)


# ---------------------------------------------------------------------------
# create_event
# ---------------------------------------------------------------------------


async def test_create_event_action_returns_success(
    tool: CalendarTool, service: CalendarService
) -> None:
    """create_event returns success and persists the event with source='agent'."""
    result = await tool.execute(
        action="create_event",
        user_id="user-1",
        title="Sprint Planning",
        start_time="2026-07-01T10:00:00+00:00",
        end_time="2026-07-01T11:00:00+00:00",
        description="Bi-weekly sprint planning",
        location="Room A",
    )
    assert result["success"] is True
    event_dict = result["event"]
    assert event_dict["title"] == "Sprint Planning"
    assert event_dict["source"] == "agent"
    assert event_dict["user_id"] == "user-1"

    # Verify persisted in DB
    fetched = await service.get_event(event_dict["id"])
    assert fetched is not None
    assert fetched.title == "Sprint Planning"
    assert fetched.source == "agent"


async def test_create_event_with_recurrence_sets_rrule(
    tool: CalendarTool, service: CalendarService
) -> None:
    """rrule param is stored correctly on the event."""
    rrule = "FREQ=WEEKLY;BYDAY=MO;COUNT=10"
    result = await tool.execute(
        action="create_event",
        user_id="user-1",
        title="Weekly Standup",
        start_time="2026-07-06T09:00:00+00:00",
        end_time="2026-07-06T09:30:00+00:00",
        rrule=rrule,
    )
    assert result["success"] is True
    assert result["event"]["rrule"] == rrule

    fetched = await service.get_event(result["event"]["id"])
    assert fetched is not None
    assert fetched.rrule == rrule


async def test_query_events_returns_list(tool: CalendarTool) -> None:
    """create 2 events, query, verify both returned."""
    await tool.execute(
        action="create_event",
        user_id="user-1",
        title="Event A",
        start_time="2026-07-01T10:00:00+00:00",
        end_time="2026-07-01T11:00:00+00:00",
    )
    await tool.execute(
        action="create_event",
        user_id="user-1",
        title="Event B",
        start_time="2026-07-02T14:00:00+00:00",
        end_time="2026-07-02T15:00:00+00:00",
    )

    result = await tool.execute(
        action="query_events",
        user_id="user-1",
    )
    assert result["success"] is True
    events = result["events"]
    assert len(events) == 2
    titles = {e["title"] for e in events}
    assert titles == {"Event A", "Event B"}


async def test_update_event_action_modifies_fields(
    tool: CalendarTool, service: CalendarService
) -> None:
    """create then update title; verify the field is modified."""
    create_result = await tool.execute(
        action="create_event",
        user_id="user-1",
        title="Original Title",
        start_time="2026-07-01T10:00:00+00:00",
        end_time="2026-07-01T11:00:00+00:00",
    )
    assert create_result["success"] is True
    event_id = create_result["event"]["id"]

    update_result = await tool.execute(
        action="update_event",
        user_id="user-1",
        event_id=event_id,
        title="Updated Title",
    )
    assert update_result["success"] is True

    fetched = await service.get_event(event_id)
    assert fetched is not None
    assert fetched.title == "Updated Title"


async def test_delete_event_action_removes_record(
    tool: CalendarTool, service: CalendarService
) -> None:
    """create then delete; verify the record is gone."""
    create_result = await tool.execute(
        action="create_event",
        user_id="user-1",
        title="To Be Deleted",
        start_time="2026-07-01T10:00:00+00:00",
        end_time="2026-07-01T11:00:00+00:00",
    )
    assert create_result["success"] is True
    event_id = create_result["event"]["id"]

    delete_result = await tool.execute(
        action="delete_event",
        user_id="user-1",
        event_id=event_id,
    )
    assert delete_result["success"] is True

    fetched = await service.get_event(event_id)
    assert fetched is None


# ---------------------------------------------------------------------------
# error paths
# ---------------------------------------------------------------------------


async def test_invalid_action_returns_error(tool: CalendarTool) -> None:
    """Unknown action returns success=False with error message."""
    result = await tool.execute(
        action="frobnicate",
        user_id="user-1",
    )
    assert result["success"] is False
    assert "Unknown action" in result["error"]


async def test_missing_required_field_returns_error(tool: CalendarTool) -> None:
    """create_event without title returns success=False."""
    result = await tool.execute(
        action="create_event",
        user_id="user-1",
        start_time="2026-07-01T10:00:00+00:00",
        end_time="2026-07-01T11:00:00+00:00",
    )
    assert result["success"] is False
    assert "Missing required field" in result["error"]
    assert "title" in result["error"]


# ---------------------------------------------------------------------------
# conversation_id
# ---------------------------------------------------------------------------


async def test_created_event_has_conversation_id(
    tool: CalendarTool, service: CalendarService
) -> None:
    """conversation_id is set from context when provided."""
    result = await tool.execute(
        action="create_event",
        user_id="user-1",
        title="Chat-Initiated Event",
        start_time="2026-07-01T10:00:00+00:00",
        end_time="2026-07-01T11:00:00+00:00",
        conversation_id="conv-abc-123",
    )
    assert result["success"] is True
    assert result["event"]["conversation_id"] == "conv-abc-123"

    fetched = await service.get_event(result["event"]["id"])
    assert fetched is not None
    assert fetched.conversation_id == "conv-abc-123"


# ---------------------------------------------------------------------------
# event_type_name resolution
# ---------------------------------------------------------------------------


async def test_create_event_with_event_type_name(
    tool: CalendarTool, service: CalendarService
) -> None:
    """create event with event_type_name='Meeting' creates the type and links it."""
    result = await tool.execute(
        action="create_event",
        user_id="user-1",
        title="Strategy Sync",
        start_time="2026-07-01T10:00:00+00:00",
        end_time="2026-07-01T11:00:00+00:00",
        event_type_name="Meeting",
    )
    assert result["success"] is True
    event_type_id = result["event"]["event_type_id"]
    assert event_type_id is not None

    # Verify the event type was created
    types = await service.list_event_types("user-1")
    meeting_types = [t for t in types if t.name == "Meeting"]
    assert len(meeting_types) == 1
    assert meeting_types[0].id == event_type_id

    # Verify the event references the type
    fetched = await service.get_event(result["event"]["id"])
    assert fetched is not None
    assert fetched.event_type_id == event_type_id


async def test_create_event_reuses_existing_event_type(
    tool: CalendarTool, service: CalendarService
) -> None:
    """If event_type_name matches an existing type, it is reused (not duplicated)."""
    # Pre-create the type
    existing = await service.create_event_type("user-1", "Meeting")

    result = await tool.execute(
        action="create_event",
        user_id="user-1",
        title="Second Meeting",
        start_time="2026-07-02T10:00:00+00:00",
        end_time="2026-07-02T11:00:00+00:00",
        event_type_name="Meeting",
    )
    assert result["success"] is True
    assert result["event"]["event_type_id"] == existing.id

    # No duplicate type created
    types = await service.list_event_types("user-1")
    meeting_types = [t for t in types if t.name == "Meeting"]
    assert len(meeting_types) == 1


# ---------------------------------------------------------------------------
# tag_names resolution
# ---------------------------------------------------------------------------


async def test_create_event_with_tag_names(
    tool: CalendarTool, service: CalendarService, tmp_path: Path
) -> None:
    """create event with tag_names=['urgent', 'work'] creates tags and links them."""
    result = await tool.execute(
        action="create_event",
        user_id="user-1",
        title="Urgent Work Task",
        start_time="2026-07-01T10:00:00+00:00",
        end_time="2026-07-01T11:00:00+00:00",
        tag_names=["urgent", "work"],
    )
    assert result["success"] is True
    event_id = result["event"]["id"]

    # Verify tags were created
    tags = await service.list_tags("user-1")
    tag_names = {t.name for t in tags}
    assert "urgent" in tag_names
    assert "work" in tag_names

    # Verify tags are linked to the event
    linked_tags = await get_event_tags(event_id, service.db_path)
    linked_names = {t.name for t in linked_tags}
    assert linked_names == {"urgent", "work"}


async def test_create_event_reuses_existing_tags(
    tool: CalendarTool, service: CalendarService
) -> None:
    """If a tag name matches an existing tag, it is reused (not duplicated)."""
    # Pre-create a tag
    existing = await service.create_tag("user-1", "urgent")

    result = await tool.execute(
        action="create_event",
        user_id="user-1",
        title="Tagged Event",
        start_time="2026-07-01T10:00:00+00:00",
        end_time="2026-07-01T11:00:00+00:00",
        tag_names=["urgent", "new-tag"],
    )
    assert result["success"] is True

    # 'urgent' should not be duplicated, 'new-tag' should be created
    tags = await service.list_tags("user-1")
    urgent_tags = [t for t in tags if t.name == "urgent"]
    assert len(urgent_tags) == 1
    assert urgent_tags[0].id == existing.id
    new_tags = [t for t in tags if t.name == "new-tag"]
    assert len(new_tags) == 1


# ---------------------------------------------------------------------------
# tool registration / schema
# ---------------------------------------------------------------------------


def test_tool_name_and_schema(tool: CalendarTool) -> None:
    """Tool has correct name and input_schema."""
    assert tool.name == "calendar"
    schema = tool.input_schema
    assert schema["type"] == "object"
    assert "action" in schema["properties"]
    assert "start_time" in schema["properties"]
    assert schema["properties"]["action"]["enum"] == [
        "create_event",
        "query_events",
        "update_event",
        "delete_event",
    ]
    assert "action" in schema["required"]
    # user_id is no longer in the schema — it's resolved internally via
    # _resolve_user_id (caller-provided or default_user_id fallback).


# ---------------------------------------------------------------------------
# Chinese relative date/time parsing (_resolve_datetime)
# ---------------------------------------------------------------------------

from datetime import datetime as _dt

from agentkit.tools.calendar_tool import _resolve_datetime


def _parse_iso(value: str | None) -> _dt | None:
    """Helper: parse _resolve_datetime output back to datetime."""
    if value is None:
        return None
    return _dt.fromisoformat(value)


def test_resolve_iso8601_passthrough() -> None:
    """ISO 8601 strings are returned unchanged."""
    iso = "2026-07-08T15:00:00+00:00"
    assert _resolve_datetime(iso) == iso


def test_resolve_none_for_empty() -> None:
    """None/empty returns None."""
    assert _resolve_datetime(None) is None
    assert _resolve_datetime("") is None
    assert _resolve_datetime("   ") is None


def test_resolve_chinese_next_week_weekday() -> None:
    """'下周三' resolves to next week's Wednesday."""
    now = _dt.now().astimezone()
    result = _parse_iso(_resolve_datetime("下周三"))
    assert result is not None
    # Should be Wednesday (weekday=2)
    assert result.weekday() == 2
    # Should be in the future, specifically next week
    delta = (result.date() - now.date()).days
    assert delta >= 7  # at least 7 days out (next week)
    assert delta <= 13  # at most 13 days out


def test_resolve_chinese_next_week_with_time() -> None:
    """'下周三下午3点' resolves to next Wednesday at 15:00."""
    result = _parse_iso(_resolve_datetime("下周三下午3点"))
    assert result is not None
    assert result.weekday() == 2
    assert result.hour == 15
    assert result.minute == 0


def test_resolve_chinese_tomorrow() -> None:
    """'明天' resolves to tomorrow."""
    now = _dt.now().astimezone()
    result = _parse_iso(_resolve_datetime("明天"))
    assert result is not None
    delta = (result.date() - now.date()).days
    assert delta == 1


def test_resolve_chinese_day_after_tomorrow() -> None:
    """'后天' resolves to day after tomorrow."""
    now = _dt.now().astimezone()
    result = _parse_iso(_resolve_datetime("后天"))
    assert result is not None
    delta = (result.date() - now.date()).days
    assert delta == 2


def test_resolve_chinese_n_days_later() -> None:
    """'3天后' resolves to 3 days from now."""
    now = _dt.now().astimezone()
    result = _parse_iso(_resolve_datetime("3天后"))
    assert result is not None
    delta = (result.date() - now.date()).days
    assert delta == 3


def test_resolve_chinese_n_hours_later() -> None:
    """'2小时后' resolves to ~2 hours from now."""
    now = _dt.now().astimezone()
    result = _parse_iso(_resolve_datetime("2小时后"))
    assert result is not None
    delta_seconds = (result - now).total_seconds()
    assert 7000 <= delta_seconds <= 7400  # ~2 hours (allow small tolerance)


def test_resolve_chinese_pm_time() -> None:
    """'下周三 15:00' (Arabic) resolves to 15:00."""
    result = _parse_iso(_resolve_datetime("下周三 15:00"))
    assert result is not None
    assert result.weekday() == 2
    assert result.hour == 15


def test_resolve_chinese_this_week() -> None:
    """'这周五' resolves to this week's Friday."""
    now = _dt.now().astimezone()
    result = _parse_iso(_resolve_datetime("这周五"))
    assert result is not None
    assert result.weekday() == 4  # Friday
    # Should be this week or next if already passed
    delta = (result.date() - now.date()).days
    assert 0 <= delta <= 7


def test_resolve_chinese_unparseable_returns_none() -> None:
    """Chinese text with no recognizable date pattern returns None (not a wrong date)."""
    assert _resolve_datetime("某个时候") is None
    assert _resolve_datetime("时间待定") is None
    assert _resolve_datetime("尽快") is None


def test_resolve_chinese_does_not_fall_through_to_dateutil() -> None:
    """Chinese+number mix must NOT fall through to dateutil fuzzy (would produce wrong date).

    Before the fix, '下周三 3pm' would be parsed by dateutil fuzzy as today's
    date at 15:00 (silently dropping the Chinese). Now it returns the correct
    next-Wednesday date.
    """
    now = _dt.now().astimezone()
    result = _parse_iso(_resolve_datetime("下周三 3pm"))
    assert result is not None
    assert result.weekday() == 2  # Wednesday, not today's weekday
    # The date should NOT be today (the old bug produced today's date)
    assert result.date() != now.date()
