"""Immich asset filtering, sorting, and URL utilities."""

from __future__ import annotations

import logging
import random
from datetime import datetime, timezone
from typing import Any

from notify_bridge_core.models.media import MediaAsset, MediaType

from .constants import ASSET_TYPE_IMAGE, ASSET_TYPE_VIDEO
from .models import ImmichAlbumData, ImmichAssetInfo, SharedLinkInfo

_LOGGER = logging.getLogger(__name__)


def filter_assets(
    assets: list[ImmichAssetInfo],
    *,
    favorite_only: bool = False,
    min_rating: int = 1,
    asset_type: str = "all",
    min_date: str | None = None,
    max_date: str | None = None,
    memory_date: str | None = None,
    city: str | None = None,
    state: str | None = None,
    country: str | None = None,
    processed_only: bool = True,
) -> list[ImmichAssetInfo]:
    """Filter Immich assets by various criteria."""
    result = list(assets)

    if processed_only:
        result = [a for a in result if a.is_processed]
    if favorite_only:
        result = [a for a in result if a.is_favorite]
    if min_rating > 1:
        result = [a for a in result if a.rating is not None and a.rating >= min_rating]
    if asset_type == "photo":
        result = [a for a in result if a.type == ASSET_TYPE_IMAGE]
    elif asset_type == "video":
        result = [a for a in result if a.type == ASSET_TYPE_VIDEO]
    if min_date:
        result = [a for a in result if a.created_at >= min_date]
    if max_date:
        result = [a for a in result if a.created_at <= max_date]

    if memory_date:
        try:
            ref_date = datetime.fromisoformat(memory_date.replace("Z", "+00:00"))
            ref_year, ref_month, ref_day = ref_date.year, ref_date.month, ref_date.day

            def matches_memory(asset: ImmichAssetInfo) -> bool:
                try:
                    asset_date = datetime.fromisoformat(
                        asset.created_at.replace("Z", "+00:00")
                    )
                    return (
                        asset_date.month == ref_month
                        and asset_date.day == ref_day
                        and asset_date.year != ref_year
                    )
                except (ValueError, AttributeError):
                    return False

            result = [a for a in result if matches_memory(a)]
        except ValueError:
            _LOGGER.warning("Invalid memory_date format: %s", memory_date)

    if city:
        city_lower = city.lower()
        result = [a for a in result if a.city and city_lower in a.city.lower()]
    if state:
        state_lower = state.lower()
        result = [a for a in result if a.state and state_lower in a.state.lower()]
    if country:
        country_lower = country.lower()
        result = [a for a in result if a.country and country_lower in a.country.lower()]

    return result


def sort_assets(
    assets: list[ImmichAssetInfo],
    order_by: str = "date",
    order: str = "descending",
) -> list[ImmichAssetInfo]:
    """Sort Immich assets by the specified field."""
    result = list(assets)

    if order_by == "random":
        random.shuffle(result)
    elif order_by == "rating":
        result = sorted(
            result,
            key=lambda a: (a.rating is None, a.rating if a.rating is not None else 0),
            reverse=(order == "descending"),
        )
    elif order_by == "name":
        result = sorted(
            result,
            key=lambda a: a.filename.lower(),
            reverse=(order == "descending"),
        )
    else:  # date
        result = sorted(
            result,
            key=lambda a: a.created_at,
            reverse=(order == "descending"),
        )

    return result


# --- Shared link URL helpers ---


def get_accessible_links(links: list[SharedLinkInfo]) -> list[SharedLinkInfo]:
    return [link for link in links if link.is_accessible]


def get_protected_links(links: list[SharedLinkInfo]) -> list[SharedLinkInfo]:
    return [link for link in links if link.has_password and not link.is_expired]


def get_public_url(external_url: str, links: list[SharedLinkInfo]) -> str | None:
    accessible = get_accessible_links(links)
    if accessible:
        return f"{external_url}/share/{accessible[0].key}"
    return None


def get_any_url(external_url: str, links: list[SharedLinkInfo]) -> str | None:
    accessible = get_accessible_links(links)
    if accessible:
        return f"{external_url}/share/{accessible[0].key}"
    non_expired = [link for link in links if not link.is_expired]
    if non_expired:
        return f"{external_url}/share/{non_expired[0].key}"
    return None


def get_protected_url(external_url: str, links: list[SharedLinkInfo]) -> str | None:
    protected = get_protected_links(links)
    if protected:
        return f"{external_url}/share/{protected[0].key}"
    return None


def get_protected_password(links: list[SharedLinkInfo]) -> str | None:
    protected = get_protected_links(links)
    if protected and protected[0].password:
        return protected[0].password
    return None


def _get_best_link_key(links: list[SharedLinkInfo]) -> str | None:
    accessible = get_accessible_links(links)
    if accessible:
        return accessible[0].key
    non_expired = [link for link in links if not link.is_expired]
    if non_expired:
        return non_expired[0].key
    return None


def get_asset_download_url(
    external_url: str, links: list[SharedLinkInfo], asset_id: str
) -> str | None:
    key = _get_best_link_key(links)
    if key:
        return f"{external_url}/api/assets/{asset_id}/original?key={key}"
    return None


def get_asset_photo_url(
    external_url: str, links: list[SharedLinkInfo], asset_id: str
) -> str | None:
    key = _get_best_link_key(links)
    if key:
        return f"{external_url}/api/assets/{asset_id}/thumbnail?size=preview&key={key}"
    return None


def get_asset_video_url(
    external_url: str, links: list[SharedLinkInfo], asset_id: str
) -> str | None:
    key = _get_best_link_key(links)
    if key:
        return f"{external_url}/api/assets/{asset_id}/video/playback?key={key}"
    return None


def build_asset_media_urls(
    external_url: str, asset_id: str, asset_type: str,
) -> tuple[str, str]:
    """Return ``(preview_url, full_url)`` for an Immich asset.

    Single source of truth for the photo-vs-video endpoint rule. Used by
    ``asset_to_media`` (notification path) and the bot command handlers
    (command path) so both always pick the transcoded ``/video/playback``
    for videos and the preview-sized thumbnail for photos — if they
    diverge, Telegram ends up delivering a still JPEG for videos in a
    media group.
    """
    is_video = asset_type == ASSET_TYPE_VIDEO
    if is_video:
        preview_url = f"{external_url}/api/assets/{asset_id}/video/playback"
    else:
        preview_url = f"{external_url}/api/assets/{asset_id}/thumbnail?size=preview"
    full_url = f"{external_url}/api/assets/{asset_id}/original"
    return preview_url, full_url


def build_asset_detail(
    asset: ImmichAssetInfo,
    external_url: str,
    shared_links: list[SharedLinkInfo],
) -> dict[str, Any]:
    """Build asset detail dictionary with all available data."""
    detail: dict[str, Any] = {
        "id": asset.id,
        "type": asset.type,
        "filename": asset.filename,
        "created_at": asset.created_at,
        "owner": asset.owner_name,
        "owner_id": asset.owner_id,
        "description": asset.description,
        "people": asset.people,
        "is_favorite": asset.is_favorite,
        "rating": asset.rating,
        "latitude": asset.latitude,
        "longitude": asset.longitude,
        "city": asset.city,
        "state": asset.state,
        "country": asset.country,
        "thumbnail_url": f"{external_url}/api/assets/{asset.id}/thumbnail",
    }

    key = _get_best_link_key(shared_links)
    if key:
        detail["url"] = f"{external_url}/share/{key}/photos/{asset.id}"
        detail["download_url"] = f"{external_url}/api/assets/{asset.id}/original?key={key}"

        if asset.type == ASSET_TYPE_VIDEO:
            detail["playback_url"] = (
                f"{external_url}/api/assets/{asset.id}/video/playback?key={key}"
            )
        elif asset.type == ASSET_TYPE_IMAGE:
            detail["photo_url"] = (
                f"{external_url}/api/assets/{asset.id}/thumbnail?size=preview&key={key}"
            )

    return detail


def asset_to_media(asset: ImmichAssetInfo, external_url: str) -> MediaAsset:
    """Convert ImmichAssetInfo to generic MediaAsset."""
    media_type = MediaType.IMAGE if asset.type == ASSET_TYPE_IMAGE else MediaType.VIDEO
    try:
        created_at = datetime.fromisoformat(asset.created_at.replace("Z", "+00:00"))
    except (ValueError, AttributeError):
        created_at = datetime.now(timezone.utc)

    # preview_url is what the notification dispatcher feeds to Telegram as the
    # actual media bytes — for videos it must be the transcoded playback (mp4),
    # not the JPEG thumbnail, or Telegram receives a JPEG labeled as video/mp4.
    preview_url, full_url = build_asset_media_urls(external_url, asset.id, asset.type)

    return MediaAsset(
        id=asset.id,
        type=media_type,
        filename=asset.filename,
        created_at=created_at,
        owner_name=asset.owner_name or None,
        description=asset.description or None,
        tags=list(asset.people),
        thumbnail_url=f"{external_url}/api/assets/{asset.id}/thumbnail",
        preview_url=preview_url,
        full_url=full_url,
        extra={
            "owner_id": asset.owner_id,
            "is_favorite": asset.is_favorite,
            "rating": asset.rating,
            "latitude": asset.latitude,
            "longitude": asset.longitude,
            "city": asset.city,
            "state": asset.state,
            "country": asset.country,
            "thumbhash": asset.thumbhash,
            # file_size = original asset bytes (from exifInfo.fileSizeInByte).
            # playback_size = bytes we will actually upload (videos: transcoded
            # /video/playback). Populated lazily at dispatch time via HEAD.
            "file_size": asset.file_size,
            "playback_size": None,
        },
    )


def collect_scheduled_assets(
    albums: dict[str, "ImmichAlbumData"],
    shared_links: dict[str, list[SharedLinkInfo]],
    external_url: str,
    *,
    limit: int = 10,
    asset_type: str = "all",
    favorite_only: bool = False,
    min_rating: int = 0,
    is_memory: bool = False,
) -> tuple[list[MediaAsset], list[dict[str, Any]]]:
    """Collect and filter assets for scheduled/memory sends.

    This is the SINGLE function used by both test dispatch and the real
    scheduled runner. Assets are filtered, randomly sampled, and converted
    to MediaAsset objects.

    Args:
        albums: Album ID → ImmichAlbumData mapping.
        shared_links: Album ID → shared links mapping.
        external_url: External domain for URL construction.
        limit: Max number of assets to return.
        asset_type: "all", "photo", or "video".
        favorite_only: Only include favorites.
        min_rating: Minimum rating filter.
        is_memory: If True, filter to "On This Day" (same month+day, previous year).

    Returns:
        (list of MediaAsset, list of collection info dicts)
    """
    now = datetime.now(timezone.utc)
    memory_date = now.isoformat() if is_memory else None

    all_eligible: list[ImmichAssetInfo] = []
    # Track which album each asset belongs to. Public URL is used to construct
    # a per-asset share link; name/internal-url are surfaced to templates so
    # combined-mode sends can attribute each row to its source album.
    asset_album_map: dict[str, tuple[str, str, str, str]] = {}
    # asset_id → (album_id, album_public_url, album_name, album_internal_url)
    collections_extra: list[dict[str, Any]] = []

    # limit=0 is the periodic-summary test path — the caller only needs
    # per-album stats (name/url/counts), not a sample of assets. Skip the
    # expensive ``filter_assets`` + sampling loop entirely; on a 50k-asset
    # album the serial scan-then-discard pattern wasted seconds per test.
    stats_only = limit <= 0

    for album_id, album in albums.items():
        links = shared_links.get(album_id, [])
        album_public_url = get_public_url(external_url, links) or ""
        album_internal_url = f"{external_url}/albums/{album_id}"

        collections_extra.append({
            "name": album.name,
            "url": album_public_url or album_internal_url,
            "public_url": album_public_url,
            "asset_count": album.asset_count,
            "shared": album.shared,
            "photo_count": album.photo_count,
            "video_count": album.video_count,
            "owner": album.owner,
        })

        if stats_only:
            continue

        filtered = filter_assets(
            list(album.assets.values()),
            favorite_only=favorite_only,
            min_rating=min_rating,
            asset_type=asset_type,
            memory_date=memory_date,
        )
        for asset in filtered:
            if asset.id not in asset_album_map:
                asset_album_map[asset.id] = (
                    album_id, album_public_url, album.name, album_internal_url,
                )
                all_eligible.append(asset)

    if stats_only:
        return [], collections_extra

    # Random sample
    if len(all_eligible) > limit:
        selected = random.sample(all_eligible, limit)
    else:
        random.shuffle(all_eligible)
        selected = all_eligible

    # Convert to MediaAsset with public URLs. Per-asset album_name/album_url
    # let combined-mode templates attribute each row to its source album —
    # critical when a tracker spans multiple albums, where the event-level
    # ``album_name`` (first album only) would be misleading.
    result: list[MediaAsset] = []
    for asset in selected:
        media = asset_to_media(asset, external_url)
        mapped = asset_album_map.get(asset.id)
        if mapped:
            _, album_pub_url, album_name, album_internal_url = mapped
        else:
            album_pub_url = album_name = album_internal_url = ""
        if album_pub_url:
            media.extra["public_url"] = f"{album_pub_url}/photos/{asset.id}"
        else:
            media.extra.setdefault("public_url", "")
        media.extra["album_name"] = album_name
        media.extra["album_url"] = album_pub_url or album_internal_url
        media.extra["album_public_url"] = album_pub_url
        result.append(media)

    return result, collections_extra
