"""Notification tracker management API routes."""

import logging

from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession

from ..auth.dependencies import get_current_user
from ..database.engine import get_session
from ..database.models import (
    EventLog,
    NotificationTarget,
    NotificationTracker,
    NotificationTrackerState,
    NotificationTrackerTarget,
    ServiceProvider,
    User,
)
from ..services.scheduler import (
    reschedule_immich_dispatch_jobs,
    schedule_tracker,
    unschedule_tracker,
)
from .helpers import get_owned_entity
from .notification_tracker_targets import _tt_response

_LOGGER = logging.getLogger(__name__)

router = APIRouter(prefix="/api/notification-trackers", tags=["notification-trackers"])


class NotificationTrackerCreate(BaseModel):
    provider_id: int
    name: str
    icon: str = ""
    collection_ids: list[str] = []
    scan_interval: int = 60
    adaptive_max_skip: int | None = None
    default_tracking_config_id: int | None = None
    default_template_config_id: int | None = None
    enabled: bool = True


class NotificationTrackerUpdate(BaseModel):
    name: str | None = None
    icon: str | None = None
    collection_ids: list[str] | None = None
    scan_interval: int | None = None
    # int | None is ambiguous for partial updates — we can't distinguish
    # "clear the field" from "don't touch". Callers send this via
    # model_dump(exclude_unset=True), so an omitted key leaves the value
    # alone and an explicit null clears it back to the adaptive-off default.
    adaptive_max_skip: int | None = None
    default_tracking_config_id: int | None = None
    default_template_config_id: int | None = None
    enabled: bool | None = None


@router.get("")
async def list_notification_trackers(
    user: User = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    # Batched loader: pull trackers, then all their tracker-target links in
    # a single query, then the referenced targets in a single query. Avoids
    # the old 1 + N + N*M pattern that ran ~60 round-trips for 10 trackers.
    result = await session.exec(
        select(NotificationTracker).where(NotificationTracker.user_id == user.id)
    )
    trackers = list(result.all())
    if not trackers:
        return []

    tracker_ids = [t.id for t in trackers]
    tt_result = await session.exec(
        select(NotificationTrackerTarget).where(
            NotificationTrackerTarget.tracker_id.in_(tracker_ids)
        )
    )
    tt_rows = list(tt_result.all())

    target_ids = {tt.target_id for tt in tt_rows}
    targets_by_id: dict[int, NotificationTarget] = {}
    if target_ids:
        tgt_result = await session.exec(
            select(NotificationTarget).where(NotificationTarget.id.in_(target_ids))
        )
        targets_by_id = {t.id: t for t in tgt_result.all()}

    tts_by_tracker: dict[int, list[NotificationTrackerTarget]] = {}
    for tt in tt_rows:
        tts_by_tracker.setdefault(tt.tracker_id, []).append(tt)

    return [
        _build_tracker_response(t, tts_by_tracker.get(t.id, []), targets_by_id)
        for t in trackers
    ]


def _build_tracker_response(
    t: NotificationTracker,
    tts: list[NotificationTrackerTarget],
    targets_by_id: dict[int, NotificationTarget],
) -> dict:
    """In-memory assembler for a tracker + its pre-loaded links/targets."""
    tracker_targets = []
    for tt in tts:
        target = targets_by_id.get(tt.target_id)
        tracker_targets.append({
            "id": tt.id,
            "tracker_id": tt.tracker_id,
            "target_id": tt.target_id,
            "target_name": target.name if target else None,
            "target_type": target.type if target else None,
            "target_icon": target.icon if target else None,
            "tracking_config_id": tt.tracking_config_id,
            "template_config_id": tt.template_config_id,
            "enabled": tt.enabled,
            "quiet_hours_start": tt.quiet_hours_start,
            "quiet_hours_end": tt.quiet_hours_end,
            "created_at": tt.created_at.isoformat(),
        })
    return {
        "id": t.id,
        "name": t.name,
        "icon": t.icon,
        "provider_id": t.provider_id,
        "collection_ids": t.collection_ids,
        "scan_interval": t.scan_interval,
        "adaptive_max_skip": t.adaptive_max_skip,
        "default_tracking_config_id": t.default_tracking_config_id,
        "default_template_config_id": t.default_template_config_id,
        "enabled": t.enabled,
        "tracker_targets": tracker_targets,
        "created_at": t.created_at.isoformat(),
    }


@router.post("", status_code=status.HTTP_201_CREATED)
async def create_notification_tracker(
    body: NotificationTrackerCreate,
    user: User = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    provider = await session.get(ServiceProvider, body.provider_id)
    if not provider or provider.user_id != user.id:
        raise HTTPException(status_code=404, detail="Provider not found")

    tracker = NotificationTracker(user_id=user.id, **body.model_dump())
    session.add(tracker)
    await session.commit()
    await session.refresh(tracker)
    # Drop the cached enabled-trackers list so the next inbound event
    # (HA / webhook) sees the new tracker without waiting out the TTL.
    from ..services.event_dispatch import invalidate_tracker_cache
    invalidate_tracker_cache(tracker.provider_id)
    if tracker.enabled:
        await schedule_tracker(
            tracker.id, tracker.scan_interval,
            adaptive_max_skip=tracker.adaptive_max_skip,
        )
    await reschedule_immich_dispatch_jobs()
    return await _tracker_response(session, tracker)


@router.get("/{tracker_id}")
async def get_notification_tracker(
    tracker_id: int,
    user: User = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    tracker = await _get_user_tracker(session, tracker_id, user.id)
    return await _tracker_response(session, tracker)


@router.put("/{tracker_id}")
async def update_notification_tracker(
    tracker_id: int,
    body: NotificationTrackerUpdate,
    user: User = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    tracker = await _get_user_tracker(session, tracker_id, user.id)
    for field, value in body.model_dump(exclude_unset=True).items():
        setattr(tracker, field, value)
    session.add(tracker)
    await session.commit()
    await session.refresh(tracker)
    from ..services.event_dispatch import invalidate_tracker_cache
    invalidate_tracker_cache(tracker.provider_id)
    if tracker.enabled:
        await schedule_tracker(
            tracker.id, tracker.scan_interval,
            adaptive_max_skip=tracker.adaptive_max_skip,
        )
    else:
        await unschedule_tracker(tracker.id)
    await reschedule_immich_dispatch_jobs()
    return await _tracker_response(session, tracker)


@router.delete("/{tracker_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_notification_tracker(
    tracker_id: int,
    user: User = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    """Delete a tracker and its child rows in three bulk statements.

    The previous implementation issued one DELETE per child row plus one
    UPDATE per event_log row, which scaled linearly with the tracker's
    history (an old, busy tracker could hit thousands of round-trips).
    Bulk DELETE/UPDATE collapses that to three SQL statements regardless
    of size.
    """
    from sqlalchemy import delete as sa_delete, update as sa_update

    tracker = await _get_user_tracker(session, tracker_id, user.id)
    # Junction rows — direct dependents of the tracker.
    await session.execute(
        sa_delete(NotificationTrackerTarget).where(
            NotificationTrackerTarget.tracker_id == tracker_id
        )
    )
    # Persisted scan state for this tracker.
    await session.execute(
        sa_delete(NotificationTrackerState).where(
            NotificationTrackerState.tracker_id == tracker_id
        )
    )
    # Preserve the audit trail in event_log; just null the back-reference
    # so the tracker row can be removed without an FK violation.
    await session.execute(
        sa_update(EventLog).where(EventLog.tracker_id == tracker_id).values(tracker_id=None)
    )
    provider_id_for_cache = tracker.provider_id
    await session.delete(tracker)
    await session.commit()
    from ..services.event_dispatch import invalidate_tracker_cache
    invalidate_tracker_cache(provider_id_for_cache)
    await unschedule_tracker(tracker_id)
    await reschedule_immich_dispatch_jobs()


@router.post("/{tracker_id}/trigger")
async def trigger_notification_tracker(
    tracker_id: int,
    user: User = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    tracker = await _get_user_tracker(session, tracker_id, user.id)
    from ..services.watcher import check_tracker
    result = await check_tracker(tracker.id)
    return {"triggered": True, "result": result}


@router.get("/{tracker_id}/history")
async def notification_tracker_history(
    tracker_id: int,
    limit: int = Query(default=20, ge=1, le=500),
    user: User = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    await _get_user_tracker(session, tracker_id, user.id)
    result = await session.exec(
        select(EventLog)
        .where(EventLog.tracker_id == tracker_id)
        .order_by(EventLog.created_at.desc())
        .limit(limit)
    )
    return [
        {
            "id": e.id,
            "event_type": e.event_type,
            "collection_id": e.collection_id,
            "collection_name": e.collection_name,
            "details": e.details,
            "created_at": e.created_at.isoformat() + ("Z" if not e.created_at.tzinfo else ""),
        }
        for e in result.all()
    ]


async def _tracker_response(session: AsyncSession, t: NotificationTracker) -> dict:
    """Build tracker response with nested tracker_targets."""
    result = await session.exec(
        select(NotificationTrackerTarget).where(NotificationTrackerTarget.tracker_id == t.id)
    )
    tracker_targets = [await _tt_response(session, tt) for tt in result.all()]

    return {
        "id": t.id,
        "name": t.name,
        "icon": t.icon,
        "provider_id": t.provider_id,
        "collection_ids": t.collection_ids,
        "scan_interval": t.scan_interval,
        "adaptive_max_skip": t.adaptive_max_skip,
        "default_tracking_config_id": t.default_tracking_config_id,
        "default_template_config_id": t.default_template_config_id,
        "enabled": t.enabled,
        "tracker_targets": tracker_targets,
        "created_at": t.created_at.isoformat(),
    }


async def _get_user_tracker(
    session: AsyncSession, tracker_id: int, user_id: int
) -> NotificationTracker:
    return await get_owned_entity(
        session, NotificationTracker, tracker_id, user_id,
        not_found_msg="Tracker not found",
    )
