"""The per-frame HTML pages: Layout (/frames/{id}, the widget placement
canvas), Configuration, and Stats, all inside the sidebar app shell.
Each widget's own settings (album, calendar view/inclusion, whiteboard
source, etc.) no longer have their own tab/page -- they're a dialog
opened from a gear icon on the widget's box in the Layout canvas (see
static/frame_layout.js), whose content this module also serves (the
/widgets/{widget_id}/dialog route) as a small HTML fragment, not a full
page. Data loading otherwise happens client-side against
/api/frames/{id}/... (routers/api_frames.py, routers/api_widgets.py);
these routes just authorize and render the scaffold."""

from __future__ import annotations

from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy import select
from sqlalchemy.orm import Session

from .. import weather
from ..auth import can_view_frame, current_user
from ..calendar_render import CALENDAR_VIEW_LABELS
from ..db import get_db
from ..image_pipeline import (
    DEFAULT_PALETTE_RGB,
    DISPLAY_MODE_LABELS,
    PALETTE_LABELS,
    STATIC_DISPLAY_MODES,
    palette_to_hex,
)
from ..models import (
    CalendarWidgetConfig,
    Frame,
    FrameCalendar,
    FrameTaskList,
    PhotoWidgetConfig,
    StaticWidgetConfig,
    TaskWidgetConfig,
    TextWidgetConfig,
    User,
    UserFrame,
    WeatherWidgetConfig,
    WhiteboardWidgetConfig,
    Widget,
)
from ..quiet_hours import ALL_TIMEZONES
from ..widgets import text as text_widget
from .common import shell_context, widget_of_type

router = APIRouter()
templates = Jinja2Templates(directory="app/templates")


def _frame_page(request: Request, db: Session, frame_id: int, template: str, tab: str, **extra):
    user = current_user(request, db)
    if user is None:
        return RedirectResponse(f"/login?next=/frames/{frame_id}", status_code=303)
    frame = db.get(Frame, frame_id)
    if frame is None or not can_view_frame(db, user, frame):
        raise HTTPException(404, "No such frame")
    ctx = shell_context(request, db, user, active_frame=frame)
    ctx.update({"frame": frame, "active_tab": tab, **extra})
    return templates.TemplateResponse(template, ctx)


@router.get("/frames/{frame_id}", response_class=HTMLResponse)
def frame_layout_page(frame_id: int, request: Request, db: Session = Depends(get_db)):
    return _frame_page(request, db, frame_id, "frame_layout.html", "layout")


@router.get("/frames/{frame_id}/config", response_class=HTMLResponse)
def frame_config_page(frame_id: int, request: Request, db: Session = Depends(get_db)):
    frame = db.get(Frame, frame_id)
    photo_widget_id = None
    if frame is not None:
        photo_widget = widget_of_type(db, frame, "photos")
        if photo_widget is not None:
            photo_widget_id = photo_widget.id
    return _frame_page(
        request, db, frame_id, "frame_config.html", "config",
        timezones=ALL_TIMEZONES,
        palette_labels=PALETTE_LABELS,
        default_palette_rgb=DEFAULT_PALETTE_RGB,
        palette_to_hex=palette_to_hex,
        photo_widget_id=photo_widget_id,
    )


@router.get("/frames/{frame_id}/stats", response_class=HTMLResponse)
def frame_stats_page(frame_id: int, request: Request, db: Session = Depends(get_db)):
    return _frame_page(request, db, frame_id, "frame_stats.html", "stats")


# --- Per-widget config dialog content -------------------------------------

def _user_available_calendars(user: User) -> list[dict]:
    """This user's full set of calendars available to add to any widget:
    the single ICS subscription (if set) plus every CalDAV calendar last
    discovered from Settings' "Discover calendars" button. Doesn't hit
    the network -- reads the cached list a user refreshes themselves."""
    calendars = []
    if user.calendar_ics_url:
        calendars.append({"key": "ics", "label": "My calendar (ICS)"})
    for c in (user.calendar_caldav_calendars or []):
        calendars.append({"key": f"caldav:{c['href']}", "label": c.get("display_name") or "Calendar"})
    return calendars


def _calendar_users_for_widget(db: Session, frame_id: int, widget_id: int, viewer_id: int | None) -> list[dict]:
    """Per-linked-user calendar list for the calendar dialog's "Included
    calendars" section. The viewer's own row lists EVERY calendar they
    have available, each with a full add/remove toggle; every other
    linked user's row lists ONLY the calendars they've already included
    (mute-only for the viewer -- see api_widgets.py's
    api_widget_calendar_select: only a calendar's owner may turn it on,
    but anyone linked to the frame may turn one off)."""
    users = db.execute(
        select(User).join(UserFrame, UserFrame.user_id == User.id)
        .where(UserFrame.frame_id == frame_id).order_by(User.username)
    ).scalars().all()
    included_by_user: dict[int, list[FrameCalendar]] = {}
    for fc in db.execute(select(FrameCalendar).where(FrameCalendar.widget_id == widget_id)).scalars().all():
        included_by_user.setdefault(fc.user_id, []).append(fc)

    result = []
    for u in users:
        is_self = u.id == viewer_id
        if is_self:
            own_rows = {fc.calendar_key: fc for fc in included_by_user.get(u.id, [])}
            calendars = [
                {**c, "included": own_rows[c["key"]].included if c["key"] in own_rows else False,
                 "color_index": own_rows[c["key"]].color_index if c["key"] in own_rows else None}
                for c in _user_available_calendars(u)
            ]
        else:
            calendars = [
                {"key": fc.calendar_key, "label": fc.calendar_label, "included": True}
                for fc in included_by_user.get(u.id, []) if fc.included
            ]
        result.append({
            "user_id": u.id, "display_name": u.display_name or u.username,
            "is_self": is_self, "calendars": calendars,
        })
    return result


def _task_users_for_widget(db: Session, frame_id: int, widget_id: int, viewer_id: int | None) -> list[dict]:
    """Per-linked-user task-list list for the tasks dialog's "Included
    task lists" section -- same shape as _calendar_users_for_widget,
    restricted to CalDAV calendars only (no "ics" option: a plain ICS
    subscription has no VTODO collection to speak of, see
    caldav_client.fetch_tasks)."""
    users = db.execute(
        select(User).join(UserFrame, UserFrame.user_id == User.id)
        .where(UserFrame.frame_id == frame_id).order_by(User.username)
    ).scalars().all()
    included_by_user: dict[int, list[FrameTaskList]] = {}
    for ftl in db.execute(select(FrameTaskList).where(FrameTaskList.widget_id == widget_id)).scalars().all():
        included_by_user.setdefault(ftl.user_id, []).append(ftl)

    result = []
    for u in users:
        is_self = u.id == viewer_id
        available = [c for c in _user_available_calendars(u) if c["key"].startswith("caldav:")]
        if is_self:
            own_rows = {ftl.calendar_key: ftl for ftl in included_by_user.get(u.id, [])}
            task_lists = [
                {**c, "included": own_rows[c["key"]].included if c["key"] in own_rows else False,
                 "color_index": own_rows[c["key"]].color_index if c["key"] in own_rows else None}
                for c in available
            ]
        else:
            task_lists = [
                {"key": ftl.calendar_key, "label": ftl.calendar_label, "included": True}
                for ftl in included_by_user.get(u.id, []) if ftl.included
            ]
        result.append({
            "user_id": u.id, "display_name": u.display_name or u.username,
            "is_self": is_self, "task_lists": task_lists,
        })
    return result


def _whiteboard_source_info(db: Session, whiteboard_cfg: WhiteboardWidgetConfig) -> dict | None:
    """Whose account this widget currently fetches with, for showing
    "using <name>'s account" to everyone linked, not just whoever set
    it. None if no source is configured."""
    if not whiteboard_cfg.user_id or not whiteboard_cfg.url:
        return None
    user = db.get(User, whiteboard_cfg.user_id)
    if user is None:
        return None
    return {"user_id": user.id, "display_name": user.display_name or user.username, "url": whiteboard_cfg.url}


WEEK_START_LABELS = {0: "Monday", 1: "Tuesday", 2: "Wednesday", 3: "Thursday",
                     4: "Friday", 5: "Saturday", 6: "Sunday"}


@router.get("/frames/{frame_id}/widgets/{widget_id}/dialog", response_class=HTMLResponse)
def widget_dialog(frame_id: int, widget_id: int, request: Request, db: Session = Depends(get_db)):
    """The gear-icon dialog's content, dispatched by widget_type -- a
    small HTML fragment (no app_base shell/tabs), fetched and injected
    into a <dialog> by static/frame_layout.js. Not itself a page a user
    would navigate to directly."""
    user = current_user(request, db)
    if user is None:
        raise HTTPException(401, "Not logged in")
    frame = db.get(Frame, frame_id)
    if frame is None or not can_view_frame(db, user, frame):
        raise HTTPException(404, "No such frame")
    widget = db.get(Widget, widget_id)
    if widget is None or widget.frame_id != frame.id:
        raise HTTPException(404, "No such widget")

    if widget.widget_type == "photos":
        photo_cfg = db.get(PhotoWidgetConfig, widget.id)
        return templates.TemplateResponse("_widget_dialog_photos.html", {
            "request": request, "frame": frame, "widget": widget, "photo_cfg": photo_cfg,
            "display_mode_labels": DISPLAY_MODE_LABELS,
        })

    if widget.widget_type == "calendar":
        calendar_cfg = db.get(CalendarWidgetConfig, widget.id)
        return templates.TemplateResponse("_widget_dialog_calendar.html", {
            "request": request, "frame": frame, "widget": widget, "calendar_cfg": calendar_cfg, "user": user,
            "calendar_views": CALENDAR_VIEW_LABELS,
            "calendar_users": _calendar_users_for_widget(db, frame.id, widget.id, user.id),
            "week_start_labels": WEEK_START_LABELS,
            "calendar_color_labels": PALETTE_LABELS,
            "default_palette_rgb": DEFAULT_PALETTE_RGB,
            "palette_to_hex": palette_to_hex,
        })

    if widget.widget_type == "tasks":
        task_cfg = db.get(TaskWidgetConfig, widget.id)
        return templates.TemplateResponse("_widget_dialog_tasks.html", {
            "request": request, "frame": frame, "widget": widget, "task_cfg": task_cfg, "user": user,
            "task_users": _task_users_for_widget(db, frame.id, widget.id, user.id),
            "task_color_labels": PALETTE_LABELS,
            "default_palette_rgb": DEFAULT_PALETTE_RGB,
            "palette_to_hex": palette_to_hex,
        })

    if widget.widget_type == "static":
        static_cfg = db.get(StaticWidgetConfig, widget.id)
        return templates.TemplateResponse("_widget_dialog_static.html", {
            "request": request, "frame": frame, "widget": widget, "static_cfg": static_cfg,
            "display_mode_labels": {k: v for k, v in DISPLAY_MODE_LABELS.items() if k in STATIC_DISPLAY_MODES},
        })

    if widget.widget_type == "text":
        text_cfg = db.get(TextWidgetConfig, widget.id)
        return templates.TemplateResponse("_widget_dialog_text.html", {
            "request": request, "frame": frame, "widget": widget, "text_cfg": text_cfg,
            "text_font_families": text_widget.FONT_FAMILIES,
        })

    if widget.widget_type == "whiteboard":
        whiteboard_cfg = db.get(WhiteboardWidgetConfig, widget.id)
        viewer_has_webdav_creds = bool(
            user.webdav_username or (user.webdav_reuse_caldav_creds and user.calendar_caldav_username)
        )
        return templates.TemplateResponse("_widget_dialog_whiteboard.html", {
            "request": request, "frame": frame, "widget": widget, "user": user,
            "whiteboard_source": _whiteboard_source_info(db, whiteboard_cfg),
            "viewer_has_webdav_creds": viewer_has_webdav_creds,
        })

    if widget.widget_type == "weather":
        weather_cfg = db.get(WeatherWidgetConfig, widget.id)
        return templates.TemplateResponse("_widget_dialog_weather.html", {
            "request": request, "frame": frame, "widget": widget, "weather_cfg": weather_cfg,
            "weather_provider_labels": weather.PROVIDER_LABELS,
        })

    raise HTTPException(400, f"Unknown widget type: {widget.widget_type}")
