"""Tests for calendar DB CRUD (U1)."""

from __future__ import annotations

import asyncio
from pathlib import Path

import pytest

from agentkit.calendar.db import (
    add_tag_to_event,
    delete_event,
    delete_event_type,
    get_event,
    get_event_tags,
    init_calendar_db,
    insert_event,
    insert_event_type,
    insert_external_config,
    insert_invitation,
    insert_reminder_rule,
    insert_tag,
    list_event_types,
    list_events,
    list_external_configs,
    list_invitations,
    list_reminder_rules_for_event,
    list_reminder_rules_for_type,
    list_tags,
    update_event,
    update_event_type,
    update_invitation_status,
)
from agentkit.calendar.models import (
    CalendarEvent,
    EventType,
    ExternalCalendarConfig,
    Invitation,
    ReminderRule,
    Tag,
    _now_iso,
)


@pytest.fixture
def db_path(tmp_path: Path) -> Path:
    path = tmp_path / "test_calendar.db"
    asyncio.run(init_calendar_db(path))
    return path


def _make_event(
    event_id: str = "evt-1",
    user_id: str = "user-1",
    title: str = "Test Event",
    start: str = "2026-07-01T10:00:00+00:00",
    end: str = "2026-07-01T11:00:00+00:00",
    **kwargs,
) -> CalendarEvent:
    now = _now_iso()
    return CalendarEvent(
        id=event_id,
        user_id=user_id,
        title=title,
        start_time=start,
        end_time=end,
        last_modified=now,
        created_at=now,
        **kwargs,
    )


# ---------------------------------------------------------------------------
# init_calendar_db
# ---------------------------------------------------------------------------


def test_init_calendar_db_creates_all_tables(db_path: Path) -> None:
    """init_calendar_db creates all 8 tables."""
    import aiosqlite

    async def check():
        async with aiosqlite.connect(str(db_path)) as db:
            cursor = await db.execute(
                "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
            )
            tables = {row[0] for row in await cursor.fetchall()}
        return tables

    tables = asyncio.run(check())
    expected = {
        "calendar_events",
        "calendar_event_types",
        "calendar_tags",
        "calendar_event_tags",
        "calendar_reminder_rules",
        "calendar_reminder_deliveries",
        "calendar_external_configs",
        "calendar_invitations",
    }
    assert expected.issubset(tables), f"Missing tables: {expected - tables}"


# ---------------------------------------------------------------------------
# Event CRUD
# ---------------------------------------------------------------------------


def test_insert_and_get_event_roundtrip(db_path: Path) -> None:
    """Insert event, fetch by id, all fields preserved including is_invited."""
    event = _make_event(is_invited=True, source="agent", conversation_id="conv-1")
    asyncio.run(insert_event(event, db_path))

    fetched = asyncio.run(get_event("evt-1", db_path))
    assert fetched is not None
    assert fetched.id == "evt-1"
    assert fetched.title == "Test Event"
    assert fetched.is_invited is True
    assert fetched.source == "agent"
    assert fetched.conversation_id == "conv-1"


def test_list_events_by_user_filtered_by_date_range(db_path: Path) -> None:
    """Insert 3 events, query range covering 2."""
    for i, day in enumerate([1, 15, 28]):
        evt = _make_event(
            event_id=f"evt-{i}",
            start=f"2026-07-{day:02d}T10:00:00+00:00",
            end=f"2026-07-{day:02d}T11:00:00+00:00",
        )
        asyncio.run(insert_event(evt, db_path))

    events = asyncio.run(
        list_events(
            "user-1",
            start="2026-07-10T00:00:00+00:00",
            end="2026-07-20T00:00:00+00:00",
            db_path=db_path,
        )
    )
    assert len(events) == 1
    assert events[0].id == "evt-1"


def test_update_event_modifies_fields(db_path: Path) -> None:
    """Insert, update title, verify."""
    asyncio.run(insert_event(_make_event(), db_path))
    updated = asyncio.run(update_event("evt-1", {"title": "Updated"}, db_path))
    assert updated is True

    fetched = asyncio.run(get_event("evt-1", db_path))
    assert fetched is not None
    assert fetched.title == "Updated"


def test_delete_event_removes_record(db_path: Path) -> None:
    """Insert, delete, verify gone."""
    asyncio.run(insert_event(_make_event(), db_path))
    deleted = asyncio.run(delete_event("evt-1", db_path))
    assert deleted is True

    fetched = asyncio.run(get_event("evt-1", db_path))
    assert fetched is None


# ---------------------------------------------------------------------------
# Event Type CRUD
# ---------------------------------------------------------------------------


def test_event_type_crud(db_path: Path) -> None:
    """Create/list/update/delete event types."""
    et = EventType(id="type-1", user_id="user-1", name="会议", color="#FF0000")
    asyncio.run(insert_event_type(et, db_path))

    types = asyncio.run(list_event_types("user-1", db_path))
    assert len(types) == 1
    assert types[0].name == "会议"
    assert types[0].color == "#FF0000"

    asyncio.run(update_event_type("type-1", {"name": "Meeting"}, db_path))
    types = asyncio.run(list_event_types("user-1", db_path))
    assert types[0].name == "Meeting"

    deleted = asyncio.run(delete_event_type("type-1", db_path))
    assert deleted is True
    types = asyncio.run(list_event_types("user-1", db_path))
    assert len(types) == 0


# ---------------------------------------------------------------------------
# Tag CRUD + many-to-many
# ---------------------------------------------------------------------------


def test_tag_many_to_many(db_path: Path) -> None:
    """Event with 3 tags, query by tag returns event."""
    asyncio.run(insert_event(_make_event(), db_path))

    for i in range(3):
        tag = Tag(id=f"tag-{i}", user_id="user-1", name=f"Tag{i}")
        asyncio.run(insert_tag(tag, db_path))
        asyncio.run(add_tag_to_event("evt-1", f"tag-{i}", db_path))

    # Get tags for event
    tags = asyncio.run(get_event_tags("evt-1", db_path))
    assert len(tags) == 3

    # List events filtered by tag
    events = asyncio.run(list_events("user-1", tag_id="tag-1", db_path=db_path))
    assert len(events) == 1
    assert events[0].id == "evt-1"

    # List all tags
    all_tags = asyncio.run(list_tags("user-1", db_path))
    assert len(all_tags) == 3


# ---------------------------------------------------------------------------
# Reminder Rule CRUD
# ---------------------------------------------------------------------------


def test_reminder_rule_crud(db_path: Path) -> None:
    """Create rule for event, create default rule for type."""
    asyncio.run(insert_event(_make_event(), db_path))
    asyncio.run(
        insert_event_type(EventType(id="type-1", user_id="user-1", name="Meeting"), db_path)
    )

    # Event-level rule
    rule1 = ReminderRule(
        id="rule-1", event_id="evt-1", offset_minutes=-15, channels=["client", "email"]
    )
    asyncio.run(insert_reminder_rule(rule1, db_path))

    # Type-level default rule
    rule2 = ReminderRule(
        id="rule-2", event_type_id="type-1", offset_minutes=-1440, channels=["email"]
    )
    asyncio.run(insert_reminder_rule(rule2, db_path))

    event_rules = asyncio.run(list_reminder_rules_for_event("evt-1", db_path))
    assert len(event_rules) == 1
    assert event_rules[0].offset_minutes == -15
    assert event_rules[0].channels == ["client", "email"]

    type_rules = asyncio.run(list_reminder_rules_for_type("type-1", db_path))
    assert len(type_rules) == 1
    assert type_rules[0].offset_minutes == -1440


# ---------------------------------------------------------------------------
# External Config
# ---------------------------------------------------------------------------


def test_external_config_stores_encrypted_credentials(db_path: Path) -> None:
    """Insert config, verify credentials field is opaque (stored as-is)."""
    config = ExternalCalendarConfig(
        id="cfg-1",
        user_id="user-1",
        provider="caldav",
        credentials='{"url":"https://caldav.icloud.com","user":"alice","pass":"xxx"}',
        sync_frequency=15,
        sync_scope=["type-1", "type-2"],
    )
    asyncio.run(insert_external_config(config, db_path))

    configs = asyncio.run(list_external_configs("user-1", db_path))
    assert len(configs) == 1
    assert configs[0].provider == "caldav"
    assert configs[0].sync_frequency == 15
    assert configs[0].sync_scope == ["type-1", "type-2"]
    # Credentials stored as-is (encryption happens at service layer)
    assert "caldav.icloud.com" in configs[0].credentials


# ---------------------------------------------------------------------------
# Invitation CRUD
# ---------------------------------------------------------------------------


def test_invitation_crud(db_path: Path) -> None:
    """Create invitation, list by email, update status."""
    asyncio.run(insert_event(_make_event(), db_path))

    inv = Invitation(
        id="inv-1",
        event_id="evt-1",
        inviter_user_id="user-1",
        invitee_email="alice@example.com",
    )
    asyncio.run(insert_invitation(inv, db_path))

    invs = asyncio.run(list_invitations("alice@example.com", db_path))
    assert len(invs) == 1
    assert invs[0].status == "pending"

    now = _now_iso()
    asyncio.run(update_invitation_status("inv-1", "accepted", now, db_path))

    invs = asyncio.run(list_invitations("alice@example.com", db_path))
    assert invs[0].status == "accepted"
    assert invs[0].responded_at == now
