"""Calendar trigger provider.

Best-effort calendar integration using configured event windows.
"""

from __future__ import annotations

import json
from collections.abc import Iterable, Mapping
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from typing import cast

from noteflow.domain.triggers.entities import TriggerSignal, TriggerSource
from noteflow.infrastructure.logging import get_logger

logger = get_logger(__name__)


@dataclass(frozen=True)
class CalendarEvent:
    """Simple calendar event window."""

    start: datetime
    end: datetime
    title: str | None = None


@dataclass
class CalendarTriggerSettings:
    """Configuration for calendar trigger detection.

    Note: This is distinct from CalendarIntegrationSettings in config/settings.py,
    which handles OAuth credentials and sync parameters.
    """

    enabled: bool
    weight: float
    lookahead_minutes: int
    lookbehind_minutes: int
    events: list[CalendarEvent]


class CalendarProvider:
    """Provide trigger signal based on calendar proximity."""

    def __init__(self, settings: CalendarTriggerSettings) -> None:
        self._settings = settings

    @property
    def source(self) -> TriggerSource:
        return TriggerSource.CALENDAR

    @property
    def max_weight(self) -> float:
        return self._settings.weight

    def is_enabled(self) -> bool:
        return self._settings.enabled

    def get_signal(self) -> TriggerSignal | None:
        if not self.is_enabled():
            return None

        if not self._settings.events:
            return None

        now = datetime.now(UTC)
        window_start = now - timedelta(minutes=self._settings.lookbehind_minutes)
        window_end = now + timedelta(minutes=self._settings.lookahead_minutes)

        return next(
            (
                TriggerSignal(
                    source=self.source,
                    weight=self.max_weight,
                    app_name=event.title,
                )
                for event in self._settings.events
                if self._event_overlaps_window(event, window_start, window_end)
            ),
            None,
        )

    @staticmethod
    def _event_overlaps_window(
        event: CalendarEvent,
        window_start: datetime,
        window_end: datetime,
    ) -> bool:
        event_start = _ensure_tz(event.start)
        event_end = _ensure_tz(event.end)
        return event_start <= window_end and event_end >= window_start


def parse_calendar_event_config(raw_events: object) -> list[CalendarEvent]:
    """Parse calendar events from config/env payloads."""
    if raw_events is None:
        return []

    if isinstance(raw_events, str):
        raw_events = _load_events_from_json(raw_events)

    if isinstance(raw_events, Mapping):
        raw_events = [cast(Mapping[str, object], raw_events)]

    if not isinstance(raw_events, Iterable):
        return []

    items = list(cast(Iterable[object], raw_events))
    events: list[CalendarEvent] = []
    for item in items:
        if isinstance(item, CalendarEvent):
            events.append(item)
            continue
        if isinstance(item, Mapping):
            item_map = cast(Mapping[str, object], item)
            start = _parse_event_datetime(item_map.get("start"))
            end = _parse_event_datetime(item_map.get("end"))
            if start and end:
                raw_title = item_map.get("title")
                title = raw_title if isinstance(raw_title, str) else None
                events.append(CalendarEvent(start=start, end=end, title=title))
    return events


def _load_events_from_json(raw: str) -> list[dict[str, object]]:
    try:
        parsed = json.loads(raw)
    except json.JSONDecodeError:
        logger.debug("Failed to parse calendar events JSON")
        return []
    if isinstance(parsed, list):
        parsed_list = cast(list[object], parsed)
        events: list[dict[str, object]] = []
        for item in parsed_list:
            if isinstance(item, Mapping):
                item_map = cast(Mapping[object, object], item)
                events.append({str(key): value for key, value in item_map.items()})
        return events
    if isinstance(parsed, Mapping):
        parsed_map = cast(Mapping[object, object], parsed)
        return [{str(key): value for key, value in parsed_map.items()}]
    return []


def _parse_event_datetime(value: object) -> datetime | None:
    if isinstance(value, datetime):
        return value
    if not isinstance(value, str) or not value:
        return None
    cleaned = value.strip()
    if cleaned.endswith("Z"):
        cleaned = f"{cleaned[:-1]}+00:00"
    try:
        return datetime.fromisoformat(cleaned)
    except ValueError:
        return None


def _ensure_tz(value: datetime) -> datetime:
    if value.tzinfo is None:
        return value.replace(tzinfo=UTC)
    return value.astimezone(UTC)
