"""Streamlit UI adapter for Media Library Viewer.

This module is intentionally thin. Reusable logic lives in:

- ``clients/`` for Jellyfin, SSH, and resource collection integrations.
- ``domain/`` for UI-independent normalization of Jellyfin/media metadata.
- ``services/`` for application services such as the SQLite media index.
- ``ui/`` for Streamlit rendering grouped by feature area.

Keeping those boundaries makes it easier to replace this Streamlit frontend with
another frontend later while preserving the backend/domain code.
"""

from __future__ import annotations

import json
import posixpath
from typing import Any

import streamlit as st

from media_library_viewer.clients.jellyfin import JellyfinClient
from media_library_viewer.clients.ssh import RemoteSSHClient
from media_library_viewer.config import load_config
from media_library_viewer.ui.dashboard import render_media_overview, render_now_playing, render_resource_dashboard
from media_library_viewer.ui.file_browser import render_file_browser, set_file_browser_path
from media_library_viewer.ui.media import render_media_tab
from media_library_viewer.ui.preview import render_ssh_tools


@st.cache_resource(show_spinner=False)
def get_jellyfin_client(base_url: str, api_key: str) -> JellyfinClient:
    """Return a cached Jellyfin client for the current server/API-key pair."""
    return JellyfinClient(base_url, api_key)


@st.cache_data(ttl=300, show_spinner=False)
def cached_users(base_url: str, api_key: str):
    """Cache the Jellyfin user list to avoid repeated auth-scoped requests."""
    return get_jellyfin_client(base_url, api_key).users()


@st.cache_resource(show_spinner=False)
def get_ssh_client(host: str, username: str, port: int, key_filename: str, password: str) -> RemoteSSHClient:
    """Return a cached SSH connection wrapper for the active remote server."""
    client = RemoteSSHClient(
        host=host,
        username=username,
        port=port,
        key_filename=key_filename or None,
        password=password or None,
    )
    client.connect()
    return client


@st.cache_data(ttl=60, show_spinner=False)
def cached_libraries(base_url: str, api_key: str, user_id: str):
    """Cache library views for the selected Jellyfin user."""
    return get_jellyfin_client(base_url, api_key).libraries(user_id)


@st.cache_data(ttl=300, show_spinner=False)
def cached_media_counts(base_url: str, api_key: str, user_id: str):
    """Cache dashboard-level media counts for movies/series/episodes."""
    return get_jellyfin_client(base_url, api_key).media_counts(user_id)


@st.cache_data(ttl=300, show_spinner=False)
def cached_library_counts(base_url: str, api_key: str, user_id: str):
    """Cache per-library item counts for dashboard breakdown."""
    client = get_jellyfin_client(base_url, api_key)
    libraries = client.libraries(user_id)
    return client.library_item_counts(user_id, libraries)


@st.cache_data(ttl=15, show_spinner=False)
def cached_active_sessions(base_url: str, api_key: str):
    """Cache active Jellyfin sessions briefly for dashboard now-playing status."""
    return get_jellyfin_client(base_url, api_key).active_sessions()


@st.cache_data(ttl=30, show_spinner=False)
def cached_dir_listing(host: str, username: str, port: int, key_filename: str, password: str, path: str):
    """Cache remote directory listings briefly for snappier browsing."""
    ssh = get_ssh_client(host, username, port, key_filename, password)
    result = ssh.list_dir(path)
    if result.exit_status != 0:
        raise RuntimeError(result.stderr or result.stdout)
    return json.loads(result.stdout)


@st.cache_data(ttl=300, show_spinner=False)
def cached_ffprobe_preview(host: str, username: str, port: int, key_filename: str, password: str, path: str):
    """Cache selected-file ffprobe previews so repeated reruns stay responsive."""
    ssh = get_ssh_client(host, username, port, key_filename, password)
    return ssh.ffprobe_json(path)


def apply_remote_path_prefix(path: str, prefix: str) -> str:
    """Apply an optional fallback prefix for Jellyfin->SSH path handoff."""
    if not path:
        return path
    normalized_prefix = (prefix or "").strip()
    if not normalized_prefix:
        return path
    normalized_prefix = normalized_prefix.rstrip("/")
    if path == normalized_prefix or path.startswith(normalized_prefix + "/"):
        return posixpath.normpath(path)
    if path.startswith("/"):
        return posixpath.normpath(normalized_prefix + path)
    return posixpath.normpath(posixpath.join(normalized_prefix, path))


def map_path_to_media_root(path: str, media_root: str) -> str:
    """Map a Jellyfin path to the configured SSH media root when possible.

    Example:
    - path: ``/media/shows/Show/E01.mkv``
    - media_root: ``/srv/media``
    - result: ``/srv/media/shows/Show/E01.mkv``

    If the path is already under ``media_root``, it is returned unchanged.
    If the final segment of ``media_root`` (e.g. ``media``) appears in the
    Jellyfin path, the prefix up to that segment is replaced by ``media_root``.
    """
    if not path:
        return path
    normalized_root = (media_root or "").strip()
    if not normalized_root:
        return path
    normalized_root = posixpath.normpath(normalized_root)

    raw_parts = [part for part in str(path).split("/") if part]
    if not raw_parts:
        return path

    path_absolute = "/" + "/".join(raw_parts)
    if path_absolute == normalized_root or path_absolute.startswith(normalized_root + "/"):
        return path_absolute

    root_anchor = posixpath.basename(normalized_root)
    if not root_anchor:
        return path

    if root_anchor in raw_parts:
        anchor_index = raw_parts.index(root_anchor)
        remainder_parts = raw_parts[anchor_index + 1 :]
        return posixpath.join(normalized_root, *remainder_parts) if remainder_parts else normalized_root

    return path


def resolve_remote_media_path(path: str, media_root: str, fallback_prefix: str) -> str:
    """Resolve Jellyfin paths to SSH-visible paths.

    Strategy:
    1. Prefer mapping to ``REMOTE_MEDIA_ROOT`` when it can anchor on the root
       basename (e.g. ``media`` in ``/srv/media``).
    2. If no mapping happened, apply optional fallback prefix.
    """
    if not path:
        return path
    mapped = map_path_to_media_root(path, media_root)
    if mapped and mapped != path:
        return mapped
    return apply_remote_path_prefix(mapped or path, fallback_prefix)


def credentials_panel():
    """Render connection settings and return normalized Jellyfin/SSH inputs."""
    cfg = load_config()
    with st.sidebar:
        st.header("Connections")
        with st.expander("Jellyfin", expanded=True):
            jellyfin_url = st.text_input("URL", value=cfg.jellyfin.url, placeholder="https://jellyfin.example.com")
            jellyfin_api_key = st.text_input("API key", value=cfg.jellyfin.api_key, type="password")
            jellyfin_user_id = st.text_input(
                "User ID override",
                value=cfg.jellyfin.user_id,
                help="Optional. API keys are not user sessions, so the app normally lists users with /Users and lets you choose one.",
            )
        with st.expander("SSH", expanded=True):
            ssh_host = st.text_input("Host", value=cfg.ssh.host)
            ssh_username = st.text_input("Username", value=cfg.ssh.username)
            ssh_port = st.number_input("Port", min_value=1, max_value=65535, value=cfg.ssh.port)
            ssh_key = st.text_input("Private key path", value=cfg.ssh.key_filename)
            ssh_password = st.text_input("Password / passphrase", value=cfg.ssh.password, type="password")
            media_root = st.text_input("Default media root", value=cfg.ssh.media_root, placeholder="/mnt/media")
            remote_path_prefix = st.text_input(
                "Path prefix for Jellyfin paths",
                value=cfg.ssh.path_prefix,
                placeholder="/srv",
                help="Fallback prefix when REMOTE_MEDIA_ROOT mapping is not enough.",
            )
    return jellyfin_url, jellyfin_api_key, jellyfin_user_id, ssh_host, ssh_username, int(ssh_port), ssh_key, ssh_password, media_root, remote_path_prefix


def main():
    """Application entrypoint used by the root ``app.py`` wrapper."""
    # Set page config per Streamlit session. Doing this at module import time is
    # unreliable here because the root wrapper imports ``main`` from this module,
    # and Python may reuse the already-imported module on browser reloads.
    st.set_page_config(page_title="Media Library Viewer", layout="wide")
    st.title("Media Library Viewer")
    st.caption("Phase 1: Jellyfin browser + SSH filesystem inspection + safe remote job templates")

    jellyfin_url, jellyfin_api_key, jellyfin_user_id, ssh_host, ssh_username, ssh_port, ssh_key, ssh_password, media_root, remote_path_prefix = credentials_panel()
    if not jellyfin_url or not jellyfin_api_key:
        st.info("Enter Jellyfin connection details in the sidebar.")
        return

    try:
        client = get_jellyfin_client(jellyfin_url, jellyfin_api_key)
        users = cached_users(jellyfin_url, jellyfin_api_key)
    except Exception as exc:
        st.error(f"Jellyfin connection failed: {exc}")
        st.caption("Note: this app uses an API key with GET /Users. /Users/Me is only reliable for user access tokens, not server API keys.")
        return

    if jellyfin_user_id:
        user_id = jellyfin_user_id
    else:
        if not users:
            st.error("No Jellyfin users returned from /Users. Set JELLYFIN_USER_ID manually.")
            return
        user_options = {f"{user.get('Name', 'Unnamed')} ({user['Id']})": user["Id"] for user in users}
        with st.sidebar:
            selected_user = st.selectbox("Jellyfin user", list(user_options.keys()))
        user_id = user_options[selected_user]

    tab_dashboard, tab_monitoring, tab_media, tab_files = st.tabs(
        ["Dashboard", "Monitoring", "Media", "File browser"]
    )

    with tab_dashboard:
        render_now_playing(cached_active_sessions, jellyfin_url, jellyfin_api_key)
        st.divider()
        if not ssh_host or not ssh_username:
            st.info("Enter SSH connection details in the sidebar for server monitoring overview.")
        else:
            ssh_args = (ssh_host, ssh_username, ssh_port, ssh_key, ssh_password)
            render_resource_dashboard(get_ssh_client, ssh_args, media_root or "/", detailed=False)
        st.divider()
        render_media_overview(cached_media_counts, cached_library_counts, jellyfin_url, jellyfin_api_key, user_id)

    with tab_monitoring:
        if not ssh_host or not ssh_username:
            st.info("Enter SSH connection details in the sidebar.")
        else:
            ssh_args = (ssh_host, ssh_username, ssh_port, ssh_key, ssh_password)
            render_resource_dashboard(get_ssh_client, ssh_args, media_root or "/", detailed=True)

    libraries = cached_libraries(jellyfin_url, jellyfin_api_key, user_id)

    def set_prefixed_file_browser_path(path: str, selected_path: str | None = None, reset_filters: bool = True) -> None:
        set_file_browser_path(
            resolve_remote_media_path(path, media_root, remote_path_prefix),
            resolve_remote_media_path(selected_path, media_root, remote_path_prefix) if selected_path else None,
            reset_filters,
        )

    with tab_media:
        render_media_tab(client, user_id, libraries, set_prefixed_file_browser_path)

    with tab_files:
        if not ssh_host or not ssh_username:
            st.info("Enter SSH connection details in the sidebar.")
            return
        ssh_args = (ssh_host, ssh_username, ssh_port, ssh_key, ssh_password)
        selected_path = render_file_browser(cached_dir_listing, ssh_args, media_root or "/")
        ssh = get_ssh_client(ssh_host, ssh_username, ssh_port, ssh_key, ssh_password)
        render_ssh_tools(ssh, ssh_args, selected_path, cached_ffprobe_preview)


if __name__ == "__main__":
    main()
