from __future__ import annotations

from dataclasses import dataclass
from datetime import date, datetime, time, timedelta, timezone
from typing import Any, Callable, Sequence


@dataclass(frozen=True)
class MeetingListParams:
    page: int
    limit: int
    platform: str | None
    meeting_type: str | None
    time_filter: str | None
    sort_by: str


@dataclass(frozen=True)
class PaginatedMeetingList:
    params: MeetingListParams
    total: int
    all_items: list[Any]
    items: list[Any]

    @property
    def pagination(self) -> dict[str, Any]:
        return {
            "page": self.params.page,
            "limit": self.params.limit,
            "total": self.total,
            "total_pages": (self.total + self.params.limit - 1) // self.params.limit,
            "has_next": self.params.page * self.params.limit < self.total,
            "next_page": self.params.page + 1 if self.params.page * self.params.limit < self.total else None,
        }


PLATFORM_ALIASES = {
    "google meet": "google_meet",
    "google_meet": "google_meet",
    "googlemeet": "google_meet",
    "microsoft teams": "microsoft_teams",
    "microsoft_teams": "microsoft_teams",
    "microsoftteams": "microsoft_teams",
    "teams": "microsoft_teams",
    "zoom": "zoom",
    "jitsi": "jitsi",
}

TYPE_ALIASES = {
    "recurring": "recurring",
    "one time": "one_time",
    "one_time": "one_time",
    "onetime": "one_time",
    "one-time": "one_time",
}

TIME_ALIASES = {
    "today": "today",
    "yesterday": "yesterday",
    "last 7days": "last_7days",
    "last 7 days": "last_7days",
    "last_7days": "last_7days",
    "last7days": "last_7days",
    "last 30days": "last_30days",
    "last 30 days": "last_30days",
    "last_30days": "last_30days",
    "last30days": "last_30days",
}

SORT_ALIASES = {
    "newest first": "newest_first",
    "oldest first": "oldest_first",
    "title(a z)": "title_asc",
    "title (a z)": "title_asc",
    "title a z": "title_asc",
    "title asc": "title_asc",
    "longest first": "longest_first",
}


def _normalize_text(value: str | None) -> str | None:
    if value is None:
        return None
    cleaned = " ".join(value.strip().lower().replace("-", " ").replace("_", " ").split())
    return cleaned or None


def _normalize_platform(platform: str | None) -> str | None:
    normalized = _normalize_text(platform)
    if normalized is None:
        return None
    return PLATFORM_ALIASES.get(normalized)


def _normalize_meeting_type(meeting_type: str | None) -> str | None:
    normalized = _normalize_text(meeting_type)
    if normalized is None:
        return None
    return TYPE_ALIASES.get(normalized)


def _normalize_time_filter(time_filter: str | None) -> str | None:
    normalized = _normalize_text(time_filter)
    if normalized is None:
        return None
    return TIME_ALIASES.get(normalized)


def _normalize_sort_by(sort_by: str | None) -> str:
    normalized = _normalize_text(sort_by)
    if normalized is None:
        return "newest_first"
    return SORT_ALIASES.get(normalized, "newest_first")


def build_meeting_list(
    items: Sequence[Any],
    *,
    page: int,
    limit: int,
    max_limit: int,
    platform: str | None,
    meeting_type: str | None,
    time_filter: str | None,
    sort_by: str | None,
    reference_datetime_getter: Callable[[Any], datetime | date | None],
    title_getter: Callable[[Any], str | None],
    duration_seconds_getter: Callable[[Any], int | None] | None = None,
    platform_getter: Callable[[Any], str | None] | None = None,
    is_recurring_getter: Callable[[Any], bool] | None = None,
    now_utc: datetime | None = None,
) -> PaginatedMeetingList:
    page = max(page, 1)
    limit = max(1, min(limit, max_limit))
    now_utc = now_utc or datetime.now(timezone.utc)
    params = MeetingListParams(
        page=page,
        limit=limit,
        platform=_normalize_platform(platform),
        meeting_type=_normalize_meeting_type(meeting_type),
        time_filter=_normalize_time_filter(time_filter),
        sort_by=_normalize_sort_by(sort_by),
    )

    filtered_items = [
        item
        for item in items
        if _matches_platform(item, params.platform, platform_getter)
        and _matches_type(item, params.meeting_type, is_recurring_getter)
        and _matches_time_filter(item, params.time_filter, reference_datetime_getter, now_utc)
    ]

    sorted_items = sorted(
        filtered_items,
        key=lambda item: _sort_key(
            item,
            params.sort_by,
            reference_datetime_getter=reference_datetime_getter,
            title_getter=title_getter,
            duration_seconds_getter=duration_seconds_getter,
            now_utc=now_utc,
        ),
    )

    offset = (params.page - 1) * params.limit
    return PaginatedMeetingList(
        params=params,
        total=len(sorted_items),
        all_items=sorted_items,
        items=sorted_items[offset: offset + params.limit],
    )


def _coerce_datetime(value: datetime | date | None, now_utc: datetime) -> datetime | None:
    if value is None:
        return None
    if isinstance(value, datetime):
        if value.tzinfo is None:
            return value.replace(tzinfo=timezone.utc)
        return value.astimezone(timezone.utc)
    return datetime.combine(value, time.min, tzinfo=timezone.utc)


def _matches_platform(
    item: Any,
    platform: str | None,
    platform_getter: Callable[[Any], str | None] | None,
) -> bool:
    if not platform:
        return True
    if not platform_getter:
        return False
    item_platform = _normalize_platform(platform_getter(item))
    return item_platform == platform


def _matches_type(
    item: Any,
    meeting_type: str | None,
    is_recurring_getter: Callable[[Any], bool] | None,
) -> bool:
    if not meeting_type:
        return True
    if not is_recurring_getter:
        return False
    is_recurring = bool(is_recurring_getter(item))
    if meeting_type == "recurring":
        return is_recurring
    if meeting_type == "one_time":
        return not is_recurring
    return True


def _matches_time_filter(
    item: Any,
    time_filter: str | None,
    reference_datetime_getter: Callable[[Any], datetime | date | None],
    now_utc: datetime,
) -> bool:
    if not time_filter:
        return True

    reference_datetime = _coerce_datetime(reference_datetime_getter(item), now_utc)
    if reference_datetime is None:
        return False

    today_start = datetime.combine(now_utc.date(), time.min, tzinfo=timezone.utc)
    tomorrow_start = today_start + timedelta(days=1)

    if time_filter == "today":
        return today_start <= reference_datetime < tomorrow_start
    if time_filter == "yesterday":
        yesterday_start = today_start - timedelta(days=1)
        return yesterday_start <= reference_datetime < today_start
    if time_filter == "last_7days":
        return today_start - timedelta(days=6) <= reference_datetime < tomorrow_start
    if time_filter == "last_30days":
        return today_start - timedelta(days=29) <= reference_datetime < tomorrow_start
    return True


def _sort_key(
    item: Any,
    sort_by: str,
    *,
    reference_datetime_getter: Callable[[Any], datetime | date | None],
    title_getter: Callable[[Any], str | None],
    duration_seconds_getter: Callable[[Any], int | None] | None,
    now_utc: datetime,
) -> tuple[Any, ...]:
    reference_datetime = _coerce_datetime(reference_datetime_getter(item), now_utc) or datetime.min.replace(tzinfo=timezone.utc)
    title = (title_getter(item) or "").strip().lower()
    duration_seconds = duration_seconds_getter(item) if duration_seconds_getter else 0
    duration_seconds = duration_seconds or 0
    item_id = _extract_item_id(item)

    if sort_by == "oldest_first":
        return (reference_datetime, item_id)
    if sort_by == "title_asc":
        return (title, reference_datetime, item_id)
    if sort_by == "longest_first":
        return (-duration_seconds, reference_datetime, item_id)
    return (-reference_datetime.timestamp(), item_id)


def _extract_item_id(item: Any) -> int:
    direct_id = getattr(item, "id", None)
    if isinstance(direct_id, int):
        return direct_id
    if isinstance(item, tuple) and len(item) > 1:
        nested_id = getattr(item[1], "id", None)
        if isinstance(nested_id, int):
            return nested_id
    return 0
