"""Data models for the calendar subsystem.

All dataclasses are DTOs carried between CalendarService, Agent tools,
REST routes, and the frontend. They mirror the ``calendar_*`` DB rows.
All timestamps are ISO 8601 UTC (see KTD-11).
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime, timezone


def _now_iso() -> str:
    """Return current UTC time as ISO 8601 string."""
    return datetime.now(timezone.utc).isoformat()


@dataclass
class EventType:
    """User-defined event type (e.g. "会议", "截止", "个人")."""

    id: str
    user_id: str
    name: str
    color: str = "#4A90D9"
    is_default: bool = False

    def to_dict(self) -> dict[str, object]:
        return {
            "id": self.id,
            "user_id": self.user_id,
            "name": self.name,
            "color": self.color,
            "is_default": self.is_default,
        }


@dataclass
class Tag:
    """User-defined tag for events."""

    id: str
    user_id: str
    name: str

    def to_dict(self) -> dict[str, object]:
        return {"id": self.id, "user_id": self.user_id, "name": self.name}


@dataclass
class CalendarEvent:
    """A calendar event.

    Attributes:
        source: "manual" | "agent" | "post_extract" — origin traceability (R15).
        is_invited: True if this event arrived via invitation (R33 — special styling).
        rrule: RFC 5545 RRULE string, e.g. "FREQ=WEEKLY;BYDAY=MO;COUNT=10".
        external_id: ID in external calendar (for sync).
        external_provider: "caldav" | "outlook" | None.
        last_modified: ISO 8601 UTC, for conflict resolution (last-write-wins).
    """

    id: str
    user_id: str
    title: str
    description: str = ""
    start_time: str = ""  # ISO 8601 UTC (KTD-11)
    end_time: str = ""  # ISO 8601 UTC
    is_all_day: bool = False
    location: str = ""
    event_type_id: str | None = None
    rrule: str | None = None
    source: str = "manual"  # "manual" | "agent" | "post_extract"
    is_invited: bool = False
    conversation_id: str | None = None
    external_id: str | None = None
    external_provider: str | None = None
    last_modified: str = ""
    created_at: str = ""

    def to_dict(self) -> dict[str, object]:
        return {
            "id": self.id,
            "user_id": self.user_id,
            "title": self.title,
            "description": self.description,
            "start_time": self.start_time,
            "end_time": self.end_time,
            "is_all_day": self.is_all_day,
            "location": self.location,
            "event_type_id": self.event_type_id,
            "rrule": self.rrule,
            "source": self.source,
            "is_invited": self.is_invited,
            "conversation_id": self.conversation_id,
            "external_id": self.external_id,
            "external_provider": self.external_provider,
            "last_modified": self.last_modified,
            "created_at": self.created_at,
        }


@dataclass
class EventTag:
    """Many-to-many junction between events and tags."""

    event_id: str
    tag_id: str

    def to_dict(self) -> dict[str, object]:
        return {"event_id": self.event_id, "tag_id": self.tag_id}


@dataclass
class ReminderRule:
    """Reminder rule — per-event or per-event-type default.

    Attributes:
        event_id: FK to events (nullable for type-level defaults).
        event_type_id: FK to event_types (for default reminders).
        offset_minutes: -15 = 15 min before, -1440 = 1 day before.
        channels: ["client", "email", "webhook"].
    """

    id: str
    event_id: str | None = None
    event_type_id: str | None = None
    offset_minutes: int = -15
    channels: list[str] = field(default_factory=lambda: ["client"])

    def to_dict(self) -> dict[str, object]:
        return {
            "id": self.id,
            "event_id": self.event_id,
            "event_type_id": self.event_type_id,
            "offset_minutes": self.offset_minutes,
            "channels": self.channels,
        }


@dataclass
class ReminderDelivery:
    """Tracks delivery status of a reminder instance."""

    id: str
    reminder_rule_id: str
    event_id: str
    scheduled_time: str  # ISO 8601 UTC
    status: str = "pending"  # "pending" | "sent" | "failed" | "read"
    channel: str = "client"
    attempts: int = 0
    last_error: str | None = None

    def to_dict(self) -> dict[str, object]:
        return {
            "id": self.id,
            "reminder_rule_id": self.reminder_rule_id,
            "event_id": self.event_id,
            "scheduled_time": self.scheduled_time,
            "status": self.status,
            "channel": self.channel,
            "attempts": self.attempts,
            "last_error": self.last_error,
        }


@dataclass
class ExternalCalendarConfig:
    """Configuration for an external calendar sync provider.

    Attributes:
        provider: "caldav" | "outlook".
        credentials: encrypted JSON (CalDAV URL+user+app_password, or OAuth refresh_token).
        sync_frequency: sync interval in minutes.
        sync_scope: event type IDs to sync, empty = all.
        sync_token: delta token for incremental sync.
    """

    id: str
    user_id: str
    provider: str  # "caldav" | "outlook"
    credentials: str = ""  # encrypted JSON
    sync_frequency: int = 30  # minutes
    sync_scope: list[str] = field(default_factory=list)
    last_sync: str | None = None
    sync_token: str | None = None

    def to_dict(self) -> dict[str, object]:
        return {
            "id": self.id,
            "user_id": self.user_id,
            "provider": self.provider,
            "credentials": "***",  # Never expose credentials
            "sync_frequency": self.sync_frequency,
            "sync_scope": self.sync_scope,
            "last_sync": self.last_sync,
            "sync_token": self.sync_token,
        }


@dataclass
class Invitation:
    """Event invitation from one user to another."""

    id: str
    event_id: str
    inviter_user_id: str
    invitee_email: str
    status: str = "pending"  # "pending" | "accepted" | "declined" | "tentative"
    responded_at: str | None = None

    def to_dict(self) -> dict[str, object]:
        return {
            "id": self.id,
            "event_id": self.event_id,
            "inviter_user_id": self.inviter_user_id,
            "invitee_email": self.invitee_email,
            "status": self.status,
            "responded_at": self.responded_at,
        }
