"""
Xtream Codes (XC) API Proxy Routes.

This module provides a stateless pass-through proxy for Xtream Codes API,
allowing users to use MediaFlow as an intermediary with any XC-compatible IPTV player.
All streams (live, VOD, series, catch-up/timeshift) are proxied without storing any data.

Configuration:
    Configure your IPTV player with:
    - Server: http://your-mediaflow-server:8888
    - Username: {base64_upstream}:{actual_username}:{api_password}
    - Password: your_xc_password

    Where:
    - base64_upstream: Base64-encoded upstream XC server URL
    - actual_username: Your actual XC username
    - api_password: Your MediaFlow API password (if configured)

    The api_password part can be omitted if MediaFlow doesn't require authentication.
"""

import base64
import logging
import re
from typing import Annotated
from urllib.parse import urljoin, urlencode, urlparse

from fastapi.responses import RedirectResponse
import aiohttp
from fastapi import APIRouter, Request, Depends, Query, Response, HTTPException

from mediaflow_proxy.configs import settings
from mediaflow_proxy.handlers import proxy_stream
from mediaflow_proxy.remuxer.media_source import HTTPMediaSource
from mediaflow_proxy.remuxer.transcode_handler import (
    handle_transcode,
    handle_transcode_hls_init,
    handle_transcode_hls_playlist,
    handle_transcode_hls_segment,
)
from mediaflow_proxy.utils.base64_utils import decode_base64_url
from mediaflow_proxy.utils.http_utils import ProxyRequestHeaders, get_proxy_headers
from mediaflow_proxy.utils.http_client import create_aiohttp_session

logger = logging.getLogger(__name__)
xtream_root_router = APIRouter()


async def _handle_xtream_transcode(request, upstream_url: str, proxy_headers, start_time: float | None):
    """Shared transcode handler for Xtream stream endpoints."""
    if not settings.enable_transcode:
        raise HTTPException(status_code=503, detail="Transcoding support is disabled")
    source = HTTPMediaSource(url=upstream_url, headers=dict(proxy_headers.request))
    await source.resolve_file_size()
    return await handle_transcode(request, source, start_time=start_time)


async def _handle_xtream_hls_playlist(request, upstream_url: str, proxy_headers):
    """Generate HLS VOD playlist for an Xtream stream."""
    if not settings.enable_transcode:
        raise HTTPException(status_code=503, detail="Transcoding support is disabled")
    from urllib.parse import quote

    source = HTTPMediaSource(url=upstream_url, headers=dict(proxy_headers.request))
    await source.resolve_file_size()

    # Build URLs using the generic proxy transcode endpoints with upstream URL
    encoded_url = quote(upstream_url, safe="")
    base_params = f"d={encoded_url}"
    original = request.query_params
    if "api_password" in original:
        base_params += f"&api_password={quote(original['api_password'], safe='')}"

    init_url = f"/proxy/transcode/init.mp4?{base_params}"
    segment_url_template = (
        f"/proxy/transcode/segment.m4s?{base_params}&seg={{seg}}&start_ms={{start_ms}}&end_ms={{end_ms}}"
    )

    return await handle_transcode_hls_playlist(
        request,
        source,
        init_url=init_url,
        segment_url_template=segment_url_template,
    )


async def _handle_xtream_hls_init(request, upstream_url: str, proxy_headers):
    """Serve fMP4 init segment for an Xtream stream."""
    if not settings.enable_transcode:
        raise HTTPException(status_code=503, detail="Transcoding support is disabled")
    source = HTTPMediaSource(url=upstream_url, headers=dict(proxy_headers.request))
    await source.resolve_file_size()
    return await handle_transcode_hls_init(request, source)


async def _handle_xtream_hls_segment(
    request,
    upstream_url: str,
    proxy_headers,
    start_ms: float,
    end_ms: float,
    seg: int | None = None,
):
    """Serve a single HLS fMP4 segment for an Xtream stream."""
    if not settings.enable_transcode:
        raise HTTPException(status_code=503, detail="Transcoding support is disabled")
    source = HTTPMediaSource(url=upstream_url, headers=dict(proxy_headers.request))
    await source.resolve_file_size()
    return await handle_transcode_hls_segment(
        request, source, start_time_ms=start_ms, end_time_ms=end_ms, segment_number=seg
    )


def decode_upstream_url(upstream_encoded: str) -> str:
    """
    Decode the base64-encoded upstream XC server URL.

    Args:
        upstream_encoded: Base64-encoded upstream server URL.

    Returns:
        The decoded upstream server URL.

    Raises:
        HTTPException: If the URL cannot be decoded.
    """
    decoded = decode_base64_url(upstream_encoded)
    if not decoded:
        raise HTTPException(
            status_code=400,
            detail="Invalid upstream server URL encoding. Must be base64-encoded.",
        )
    # Ensure the URL has a trailing slash for proper URL joining
    if not decoded.endswith("/"):
        decoded += "/"
    return decoded


def decode_base64_username(encoded: str) -> str | None:
    """
    Try to decode a base64-encoded username string.

    Args:
        encoded: The potentially base64-encoded string.

    Returns:
        The decoded string if successful, None otherwise.
    """
    try:
        # Handle URL-safe base64 encoding (replace - with + and _ with /)
        url_safe_encoded = encoded.replace("-", "+").replace("_", "/")

        # Add padding if necessary
        missing_padding = len(url_safe_encoded) % 4
        if missing_padding:
            url_safe_encoded += "=" * (4 - missing_padding)

        # Decode the base64 string
        decoded_bytes = base64.b64decode(url_safe_encoded)
        decoded = decoded_bytes.decode("utf-8")

        # Check if it looks like our format (contains colons and starts with http)
        if ":" in decoded:
            return decoded

        return None
    except (base64.binascii.Error, UnicodeDecodeError, ValueError):
        return None


def parse_username_with_upstream(username: str) -> tuple[str, str, str | None]:
    """
    Parse username that contains encoded upstream URL and optional API password.

    Supports two formats:
    1. Base64-encoded format (NEW - recommended for IPTV apps):
       Username is base64({upstream_url}:{actual_username}:{api_password})
       Or base64({upstream_url}:{actual_username})

    2. Legacy colon-separated format:
       {base64_upstream}:{actual_username}:{api_password}
       Or {base64_upstream}:{actual_username}

    Args:
        username: The username field which contains upstream URL and optionally API password.

    Returns:
        Tuple of (upstream_base_url, actual_username, api_password or None).

    Raises:
        HTTPException: If format is invalid.
    """
    # First, try to decode the entire username as base64
    # This is the new format where the whole string is base64-encoded
    decoded_username = decode_base64_username(username)

    if decoded_username:
        # Successfully decoded base64, now parse the decoded string
        parts = decoded_username.split(":")
        logger.debug(f"Decoded base64 username, found {len(parts)} parts")

        # The decoded format is: {upstream_url}:{actual_username}:{api_password}
        # or {upstream_url}:{actual_username}
        # Note: upstream_url contains "://" so we need to handle that

        # Find the protocol separator
        if "://" not in decoded_username:
            raise HTTPException(
                status_code=400,
                detail="Invalid username format. Decoded base64 doesn't contain valid upstream URL.",
            )

        # Split on :// first to get protocol
        proto_split = decoded_username.split("://", 1)
        if len(proto_split) != 2:
            raise HTTPException(
                status_code=400,
                detail="Invalid username format. Could not parse upstream URL protocol.",
            )

        protocol = proto_split[0]
        rest = proto_split[1]

        # Now split the rest by colons
        rest_parts = rest.split(":")

        if len(rest_parts) == 2:
            # Format: protocol://host:actual_username (no api_password, no port in URL)
            host, actual_username = rest_parts
            upstream_url = f"{protocol}://{host}"
            api_password = None
        elif len(rest_parts) == 3:
            # Could be:
            # - protocol://host:port:actual_username (no api_password)
            # - protocol://host:actual_username:api_password (no port in URL)
            # We need to determine which case by checking if the second part looks like a port
            if rest_parts[1].isdigit() and len(rest_parts[1]) <= 5:
                # Looks like a port: protocol://host:port:actual_username
                host, port, actual_username = rest_parts
                upstream_url = f"{protocol}://{host}:{port}"
                api_password = None
            else:
                # No port: protocol://host:actual_username:api_password
                host, actual_username, api_password = rest_parts
                upstream_url = f"{protocol}://{host}"
                api_password = api_password if api_password else None
        elif len(rest_parts) == 4:
            # Format: protocol://host:port:actual_username:api_password
            host, port, actual_username, api_password = rest_parts
            upstream_url = f"{protocol}://{host}:{port}"
            api_password = api_password if api_password else None
        else:
            raise HTTPException(
                status_code=400,
                detail="Invalid username format. Could not parse base64-decoded username.",
            )

        # Ensure trailing slash for URL joining
        if not upstream_url.endswith("/"):
            upstream_url += "/"

        logger.info(f"Parsed base64 username: upstream={upstream_url}, user={actual_username}")
        return upstream_url, actual_username, api_password

    # Legacy format: {base64_upstream}:{actual_username}:{api_password}
    if ":" not in username:
        raise HTTPException(
            status_code=400,
            detail="Invalid username format. Expected base64-encoded username or legacy format: {base64_upstream}:{actual_username}:{api_password}",
        )

    parts = username.split(":")

    if len(parts) == 2:
        # Format: {base64_upstream}:{actual_username}
        upstream_encoded, actual_username = parts
        api_password = None
    elif len(parts) == 3:
        # Format: {base64_upstream}:{actual_username}:{api_password}
        upstream_encoded, actual_username, api_password = parts
        api_password = api_password if api_password else None
    else:
        raise HTTPException(
            status_code=400,
            detail="Invalid username format. Expected base64-encoded username or legacy format: {base64_upstream}:{actual_username}:{api_password}",
        )

    upstream_base = decode_upstream_url(upstream_encoded)

    logger.info(f"Parsed legacy username: upstream={upstream_base}, user={actual_username}")
    return upstream_base, actual_username, api_password


def verify_xc_api_password(api_password: str | None):
    """
    Verify the API password for XC endpoints.

    Args:
        api_password: The API password from the username field.

    Raises:
        HTTPException: If API password is required but not provided or invalid.
    """
    # If no API password is configured on the server, allow access
    if not settings.api_password:
        return

    # If API password is required but not provided
    if not api_password:
        raise HTTPException(
            status_code=403,
            detail="API password required. Username format: {base64_upstream}:{actual_username}:{api_password}",
        )

    # Verify the password matches
    if api_password != settings.api_password:
        raise HTTPException(
            status_code=403,
            detail="Invalid API password",
        )


def get_mediaflow_base_url(request: Request) -> str:
    """
    Get the MediaFlow base URL for URL rewriting.

    Args:
        request: The incoming FastAPI request.

    Returns:
        The MediaFlow base URL.
    """
    scheme = request.headers.get("x-forwarded-proto", request.url.scheme)
    host = request.headers.get("x-forwarded-host", request.headers.get("host", request.url.netloc))
    return f"{scheme}://{host}"


def encode_username_for_rewrite(upstream_base: str, actual_username: str, api_password: str | None) -> str:
    """
    Create a base64-encoded username token for URL rewriting.

    Args:
        upstream_base: The upstream XC server base URL.
        actual_username: The actual XC username.
        api_password: The MediaFlow API password (if any).

    Returns:
        A base64-encoded username string.
    """
    # Remove trailing slash from upstream for cleaner encoding
    upstream_clean = upstream_base.rstrip("/")

    # Build the combined string
    if api_password:
        combined = f"{upstream_clean}:{actual_username}:{api_password}"
    else:
        combined = f"{upstream_clean}:{actual_username}"

    # Base64 encode (URL-safe)
    encoded = base64.urlsafe_b64encode(combined.encode()).decode().rstrip("=")
    return encoded


def rewrite_urls_for_api(
    content: str,
    upstream_base: str,
    mediaflow_base: str,
    actual_username: str,
    api_password: str | None,
) -> str:
    """
    Rewrite stream URLs in API responses to route through MediaFlow.

    This function replaces the upstream username in stream URLs with a base64-encoded
    token containing upstream URL + username + api_password, so MediaFlow can properly
    route the requests.

    Args:
        content: The API response content.
        upstream_base: The upstream XC server base URL.
        mediaflow_base: The MediaFlow base URL.
        actual_username: The actual XC username (to be replaced in URLs).
        api_password: The MediaFlow API password (if any).

    Returns:
        The content with rewritten URLs.
    """

    # Parse the upstream URL to get the origin for replacement
    parsed = urlparse(upstream_base)
    upstream_origin = f"{parsed.scheme}://{parsed.netloc}"

    # Create the encoded username token for MediaFlow
    encoded_username = encode_username_for_rewrite(upstream_base, actual_username, api_password)

    # Pattern to match stream URLs with username in path
    # Matches: http(s)://host(:port)/path/{username}/password/...
    # We need to replace {username} with {encoded_username}

    # First, handle the common XC stream URL patterns where username appears in the path
    # Pattern: /{prefix}/{username}/{password}/ where prefix is live, movie, series, etc.
    # or /{username}/{password}/ for short format

    # Escape special regex characters in the origin and username
    escaped_origin = re.escape(upstream_origin)
    escaped_username = re.escape(actual_username)

    # Pattern for URLs like: https://upstream/live/{username}/{password}/...
    # or https://upstream/{username}/{password}/...
    # We want to replace the upstream origin AND the username in one go

    def replace_stream_url(match):
        """Replace upstream origin with mediaflow and username with encoded token."""
        full_url = match.group(0)
        # Replace the upstream origin with mediaflow base
        new_url = full_url.replace(upstream_origin, mediaflow_base, 1)
        # Replace the username in the path with encoded username
        # The username appears after a / and before another /
        new_url = re.sub(
            r"(/(live|movie|series|timeshift|hlsr|hls)?/)" + escaped_username + r"/",
            r"\1" + encoded_username + "/",
            new_url,
        )
        # Also handle short format: /{username}/{password}/
        new_url = re.sub(
            r"^(" + re.escape(mediaflow_base) + ")/" + escaped_username + r"/([^/]+/\d+\.)",
            r"\1/" + encoded_username + r"/\2",
            new_url,
        )
        return new_url

    # Find and replace all URLs that contain the upstream origin
    # Match URLs that start with the upstream origin and contain the username
    url_pattern = escaped_origin + r'[^"\s\\]*' + escaped_username + r'[^"\s\\]*'
    content = re.sub(url_pattern, replace_stream_url, content)

    # Handle escaped URLs in JSON (where / is escaped as \/)
    escaped_upstream_json = upstream_origin.replace("/", "\\/")
    escaped_mediaflow_json = mediaflow_base.replace("/", "\\/")
    escaped_username_json = actual_username.replace("/", "\\/")

    def replace_escaped_stream_url(match):
        """Replace escaped upstream origin with mediaflow and username with encoded token."""
        full_url = match.group(0)
        new_url = full_url.replace(escaped_upstream_json, escaped_mediaflow_json, 1)
        # Replace username (handling escaped slashes)
        new_url = re.sub(
            r"(\\/(?:live|movie|series|timeshift|hlsr|hls)?\\/)" + re.escape(escaped_username_json) + r"\\/",
            r"\1" + encoded_username + "\\/",
            new_url,
        )
        # Short format
        new_url = re.sub(
            r"^("
            + re.escape(escaped_mediaflow_json)
            + ")\\/"
            + re.escape(escaped_username_json)
            + r"\\/([^\\/]+\\/\d+\.)",
            r"\1\\/" + encoded_username + r"\\/\2",
            new_url,
        )
        return new_url

    escaped_url_pattern = re.escape(escaped_upstream_json) + r'[^"\s]*' + re.escape(escaped_username_json) + r'[^"\s]*'
    content = re.sub(escaped_url_pattern, replace_escaped_stream_url, content)

    # Also do a simple domain replacement for any remaining URLs that don't have username in path
    # (like server_info URLs)
    content = content.replace(upstream_origin, mediaflow_base)
    content = content.replace(escaped_upstream_json, escaped_mediaflow_json)

    # Also replace hostname-only version (without port) if the upstream has a non-standard port
    # This handles cases where server_info.url doesn't include the port
    if parsed.port and parsed.port not in (80, 443):
        upstream_host_only = f"{parsed.scheme}://{parsed.hostname}"
        escaped_host_only_json = upstream_host_only.replace("/", "\\/")
        content = content.replace(upstream_host_only, mediaflow_base)
        content = content.replace(escaped_host_only_json, escaped_mediaflow_json)

    # IMPORTANT: Rewrite user_info.username in the response
    # Some IPTV players (like Tivimate) use the username from the response for subsequent API calls
    # So we need to replace the actual username with the encoded username in user_info
    # Pattern: "username":"actual_username" -> "username":"encoded_username"
    content = re.sub(
        r'"username"\s*:\s*"' + escaped_username + r'"',
        f'"username":"{encoded_username}"',
        content,
    )

    return content


async def forward_api_request(
    upstream_url: str,
    request: Request,
    upstream_base: str,
    actual_username: str,
    api_password: str | None,
) -> Response:
    """
    Forward an API request to upstream XC server.

    Args:
        upstream_url: The full upstream URL.
        request: The incoming FastAPI request.
        upstream_base: The decoded upstream base URL.
        actual_username: The actual XC username (for URL rewriting).
        api_password: The MediaFlow API password (for URL rewriting).

    Returns:
        The response from upstream with URLs rewritten.
    """
    mediaflow_base = get_mediaflow_base_url(request)

    async with create_aiohttp_session(upstream_url) as (session, proxy_url):
        try:
            async with session.get(upstream_url, proxy=proxy_url, allow_redirects=True) as response:
                response.raise_for_status()

                content = await response.text()
                content_type = response.headers.get("content-type", "application/json")

                # Rewrite URLs in JSON responses
                if "json" in content_type.lower():
                    content = rewrite_urls_for_api(
                        content, upstream_base, mediaflow_base, actual_username, api_password
                    )

                return Response(
                    content=content,
                    status_code=response.status,
                    media_type=content_type,
                )
        except aiohttp.ClientResponseError as e:
            logger.error(f"Upstream XC API error: {e.status}")
            raise HTTPException(
                status_code=e.status,
                detail=f"Upstream XC server error: {e.status}",
            )
        except aiohttp.ClientError as e:
            logger.error(f"Failed to connect to upstream XC server: {e}")
            raise HTTPException(
                status_code=502,
                detail=f"Failed to connect to upstream XC server: {str(e)}",
            )


# =============================================================================
# XC API Endpoints
# =============================================================================


@xtream_root_router.get("/player_api.php")
async def player_api(
    request: Request,
    username: str = Query(..., description="Format: {base64_upstream}:{actual_username}:{api_password}"),
    password: str = Query(..., description="XC password"),
    action: str = Query(None, description="API action"),
):
    """
    Player API endpoint for IPTV player compatibility.

    Handles all XC API actions including authentication, categories, streams, and EPG.

    Args:
        request: The incoming FastAPI request.
        username: Combined upstream URL, username, and API password.
        password: XC password.
        action: The API action to perform.

    Returns:
        The API response with stream URLs rewritten.
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    # Build query params for upstream (with actual username)
    query_params = {"username": actual_username, "password": password}
    if action:
        query_params["action"] = action

    # Add any other query params except our special ones
    for k, v in request.query_params.items():
        if k not in ("username", "password", "action", "api_password"):
            query_params[k] = v

    upstream_url = f"{upstream_base}player_api.php?{urlencode(query_params)}"

    logger.info(f"XC player_api.php: action={action}, upstream={upstream_base}, user={actual_username}")

    return await forward_api_request(upstream_url, request, upstream_base, actual_username, api_password)


@xtream_root_router.get("/xmltv.php")
async def xmltv_api(
    request: Request,
    username: str = Query(..., description="Format: {base64_upstream}:{actual_username}:{api_password}"),
    password: str = Query(..., description="XC password"),
):
    """
    XMLTV/EPG endpoint for electronic program guide data.

    Args:
        request: The incoming FastAPI request.
        username: Combined upstream URL, username, and API password.
        password: XC password.

    Returns:
        The EPG XML data from upstream.
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    # Build query params for upstream
    query_params = {"username": actual_username, "password": password}
    for k, v in request.query_params.items():
        if k not in ("username", "password", "api_password"):
            query_params[k] = v

    upstream_url = f"{upstream_base}xmltv.php?{urlencode(query_params)}"

    logger.info(f"XC xmltv.php: upstream={upstream_base}")

    async with create_aiohttp_session(upstream_url, timeout=60) as (session, proxy_url):
        try:
            async with session.get(upstream_url, proxy=proxy_url, allow_redirects=True) as response:
                response.raise_for_status()

                return Response(
                    content=await response.read(),
                    status_code=response.status,
                    media_type=response.headers.get("content-type", "application/xml"),
                )
        except aiohttp.ClientResponseError as e:
            raise HTTPException(status_code=e.status, detail=f"Upstream error: {e.status}")
        except aiohttp.ClientError as e:
            raise HTTPException(status_code=502, detail=f"Failed to connect: {str(e)}")


@xtream_root_router.get("/get.php")
async def get_playlist(
    request: Request,
    username: str = Query(..., description="Format: base64({upstream}:{actual_username}:{api_password})"),
    password: str = Query(..., description="XC password"),
    type: str = Query("m3u_plus", description="Playlist type (m3u, m3u_plus)"),
    output: str = Query("ts", description="Output format (ts, m3u8)"),
):
    """
    M3U playlist generation endpoint (XC API v1).

    Redirects to /proxy/hls/manifest.m3u8 which handles M3U URL rewriting.

    Args:
        request: The incoming FastAPI request.
        username: Combined upstream URL, username, and API password.
        password: XC password.
        type: Playlist type (m3u, m3u_plus).
        output: Output stream format (ts, m3u8).

    Returns:
        Redirect to HLS proxy with upstream get.php URL.
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    # Build query params for upstream get.php
    query_params = {"username": actual_username, "password": password, "type": type, "output": output}
    for k, v in request.query_params.items():
        if k not in ("username", "password", "type", "output", "api_password"):
            query_params[k] = v

    upstream_url = f"{upstream_base}get.php?{urlencode(query_params)}"

    logger.info(f"XC get.php: type={type}, output={output}, upstream={upstream_base}, user={actual_username}")

    # Redirect to HLS proxy which handles M3U URL rewriting
    mediaflow_base = get_mediaflow_base_url(request)
    hls_params = {"d": upstream_url}
    if api_password:
        hls_params["api_password"] = api_password

    redirect_url = f"{mediaflow_base}/proxy/hls/manifest.m3u8?{urlencode(hls_params)}"
    return RedirectResponse(url=redirect_url, status_code=302)


@xtream_root_router.get("/panel_api.php")
async def panel_api(
    request: Request,
    username: str = Query(..., description="Format: {base64_upstream}:{actual_username}:{api_password}"),
    password: str = Query(..., description="XC password"),
):
    """
    Panel API endpoint (alternative API used by some XC implementations).

    Args:
        request: The incoming FastAPI request.
        username: Combined upstream URL, username, and API password.
        password: XC password.

    Returns:
        The API response with stream URLs rewritten.
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    query_params = {"username": actual_username, "password": password}
    for k, v in request.query_params.items():
        if k not in ("username", "password", "api_password"):
            query_params[k] = v

    upstream_url = f"{upstream_base}panel_api.php?{urlencode(query_params)}"

    logger.info(f"XC panel_api.php: upstream={upstream_base}")
    return await forward_api_request(upstream_url, request, upstream_base, actual_username, api_password)


# =============================================================================
# Stream Proxy Endpoints
# =============================================================================


@xtream_root_router.head("/live/{username}/{password}/{stream_id}.{ext}")
@xtream_root_router.get("/live/{username}/{password}/{stream_id}.{ext}")
async def live_stream(
    username: str,
    password: str,
    stream_id: str,
    ext: str,
    request: Request,
    proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
):
    """
    Live stream endpoint.

    Username format: {base64_upstream}:{actual_username}:{api_password}
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    stream_path = f"live/{actual_username}/{password}/{stream_id}.{ext}"
    upstream_url = urljoin(upstream_base, stream_path)

    logger.info(f"XC live stream: {stream_path}")

    # For m3u8, redirect to HLS proxy
    if ext in ("m3u8", "m3u"):
        scheme = request.headers.get("x-forwarded-proto", request.url.scheme)
        host = request.headers.get("x-forwarded-host", request.headers.get("host", request.url.netloc))
        hls_params = {"d": upstream_url}
        if api_password:
            hls_params["api_password"] = api_password
        redirect_url = f"{scheme}://{host}/proxy/hls/manifest.m3u8?{urlencode(hls_params)}"
        return RedirectResponse(url=redirect_url, status_code=302)

    return await proxy_stream(request.method, upstream_url, proxy_headers)


@xtream_root_router.head("/movie/{username}/{password}/{stream_id}.{ext}")
@xtream_root_router.get("/movie/{username}/{password}/{stream_id}.{ext}")
async def movie_stream(
    username: str,
    password: str,
    stream_id: str,
    ext: str,
    request: Request,
    proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
    transcode: bool = Query(False, description="Transcode to browser-compatible fMP4"),
    hls: bool = Query(False, description="Generate HLS VOD playlist for transcode (seekable)"),
    hls_init: bool = Query(False, description="Serve fMP4 init segment"),
    seg: int | None = Query(None, description="HLS segment number (informational)"),
    start_ms: float | None = Query(None, description="HLS segment start time in milliseconds"),
    end_ms: float | None = Query(None, description="HLS segment end time in milliseconds"),
    start: float | None = Query(None, description="Seek start time in seconds (transcode mode)"),
):
    """
    VOD/movie stream endpoint.
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    stream_path = f"movie/{actual_username}/{password}/{stream_id}.{ext}"
    upstream_url = urljoin(upstream_base, stream_path)

    logger.info(f"XC movie stream: {stream_path}")

    if hls:
        return await _handle_xtream_hls_playlist(request, upstream_url, proxy_headers)
    if hls_init:
        return await _handle_xtream_hls_init(request, upstream_url, proxy_headers)
    if (start_ms is None) != (end_ms is None):
        raise HTTPException(status_code=400, detail="Both start_ms and end_ms are required for segment requests")
    if start_ms is not None and end_ms is not None:
        return await _handle_xtream_hls_segment(request, upstream_url, proxy_headers, start_ms, end_ms, seg)
    if transcode:
        return await _handle_xtream_transcode(request, upstream_url, proxy_headers, start)

    return await proxy_stream(request.method, upstream_url, proxy_headers)


@xtream_root_router.head("/series/{username}/{password}/{stream_id}.{ext}")
@xtream_root_router.get("/series/{username}/{password}/{stream_id}.{ext}")
async def series_stream(
    username: str,
    password: str,
    stream_id: str,
    ext: str,
    request: Request,
    proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
    transcode: bool = Query(False, description="Transcode to browser-compatible fMP4"),
    hls: bool = Query(False, description="Generate HLS VOD playlist for transcode (seekable)"),
    hls_init: bool = Query(False, description="Serve fMP4 init segment"),
    seg: int | None = Query(None, description="HLS segment number (informational)"),
    start_ms: float | None = Query(None, description="HLS segment start time in milliseconds"),
    end_ms: float | None = Query(None, description="HLS segment end time in milliseconds"),
    start: float | None = Query(None, description="Seek start time in seconds (transcode mode)"),
):
    """
    Series/episode stream endpoint.
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    stream_path = f"series/{actual_username}/{password}/{stream_id}.{ext}"
    upstream_url = urljoin(upstream_base, stream_path)

    logger.info(f"XC series stream: {stream_path}")

    if hls:
        return await _handle_xtream_hls_playlist(request, upstream_url, proxy_headers)
    if hls_init:
        return await _handle_xtream_hls_init(request, upstream_url, proxy_headers)
    if (start_ms is None) != (end_ms is None):
        raise HTTPException(status_code=400, detail="Both start_ms and end_ms are required for segment requests")
    if start_ms is not None and end_ms is not None:
        return await _handle_xtream_hls_segment(request, upstream_url, proxy_headers, start_ms, end_ms, seg)
    if transcode:
        return await _handle_xtream_transcode(request, upstream_url, proxy_headers, start)

    return await proxy_stream(request.method, upstream_url, proxy_headers)


@xtream_root_router.head("/timeshift/{username}/{password}/{duration}/{start}/{stream_id}.{ext}")
@xtream_root_router.get("/timeshift/{username}/{password}/{duration}/{start}/{stream_id}.{ext}")
async def timeshift_stream(
    username: str,
    password: str,
    duration: str,
    start: str,
    stream_id: str,
    ext: str,
    request: Request,
    proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
    transcode: bool = Query(False, description="Transcode to browser-compatible fMP4"),
    seek: float | None = Query(None, description="Seek start time in seconds (transcode mode)"),
):
    """
    Timeshift/catch-up stream endpoint.
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    stream_path = f"timeshift/{actual_username}/{password}/{duration}/{start}/{stream_id}.{ext}"
    upstream_url = urljoin(upstream_base, stream_path)

    logger.info(f"XC timeshift stream: {stream_path}")

    if transcode:
        return await _handle_xtream_transcode(request, upstream_url, proxy_headers, seek)

    return await proxy_stream(request.method, upstream_url, proxy_headers)


@xtream_root_router.head("/streaming/timeshift.php")
@xtream_root_router.get("/streaming/timeshift.php")
async def timeshift_php(
    request: Request,
    proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
    username: str = Query(..., description="Format: {base64_upstream}:{actual_username}:{api_password}"),
    password: str = Query(..., description="XC password"),
    stream: str = Query(..., description="Stream ID"),
    start: str = Query(..., description="Start time"),
    duration: str = Query(None, description="Duration in minutes"),
):
    """
    Timeshift.php catch-up endpoint (alternative format).
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    # Build query params for upstream
    query_params = {"username": actual_username, "password": password, "stream": stream, "start": start}
    if duration:
        query_params["duration"] = duration
    for k, v in request.query_params.items():
        if k not in ("username", "password", "stream", "start", "duration", "api_password"):
            query_params[k] = v

    upstream_url = f"{upstream_base}streaming/timeshift.php?{urlencode(query_params)}"

    logger.info(f"XC timeshift.php: stream={stream}, start={start}")

    return await proxy_stream(request.method, upstream_url, proxy_headers)


@xtream_root_router.head("/hlsr/{token}/{username}/{password}/{channel_id}/{start}/{end}/index.m3u8")
@xtream_root_router.get("/hlsr/{token}/{username}/{password}/{channel_id}/{start}/{end}/index.m3u8")
async def hlsr_catchup(
    token: str,
    username: str,
    password: str,
    channel_id: str,
    start: str,
    end: str,
    request: Request,
    proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
):
    """
    HLSR catch-up stream endpoint.
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    stream_path = f"hlsr/{token}/{actual_username}/{password}/{channel_id}/{start}/{end}/index.m3u8"
    upstream_url = urljoin(upstream_base, stream_path)

    logger.info(f"XC HLSR catch-up: channel={channel_id}")

    # Redirect to HLS proxy for proper m3u8 handling
    scheme = request.headers.get("x-forwarded-proto", request.url.scheme)
    host = request.headers.get("x-forwarded-host", request.headers.get("host", request.url.netloc))
    hls_params = {"d": upstream_url}
    if api_password:
        hls_params["api_password"] = api_password
    redirect_url = f"{scheme}://{host}/proxy/hls/manifest.m3u8?{urlencode(hls_params)}"
    return RedirectResponse(url=redirect_url, status_code=302)


@xtream_root_router.head("/hls/{token}/{stream_id}.m3u8")
@xtream_root_router.get("/hls/{token}/{stream_id}.m3u8")
async def hls_stream(
    token: str,
    stream_id: str,
    request: Request,
    proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
):
    """
    HLS stream endpoint with token authentication.

    Note: This endpoint doesn't use the username format since it's token-based.
    The api_password should be passed as a query parameter if required.
    """
    # For token-based HLS, check api_password from query params
    api_password = request.query_params.get("api_password")
    if settings.api_password and api_password != settings.api_password:
        raise HTTPException(status_code=403, detail="Invalid API password")

    # Get upstream from query params (must be base64-encoded)
    upstream_encoded = request.query_params.get("upstream")
    if not upstream_encoded:
        raise HTTPException(status_code=400, detail="Missing 'upstream' query parameter")

    upstream_base = decode_upstream_url(upstream_encoded)
    stream_path = f"hls/{token}/{stream_id}.m3u8"
    upstream_url = urljoin(upstream_base, stream_path)

    logger.info(f"XC HLS stream: {stream_path}")

    # Redirect to HLS proxy
    scheme = request.headers.get("x-forwarded-proto", request.url.scheme)
    host = request.headers.get("x-forwarded-host", request.headers.get("host", request.url.netloc))
    hls_params = {"d": upstream_url}
    if api_password:
        hls_params["api_password"] = api_password
    redirect_url = f"{scheme}://{host}/proxy/hls/manifest.m3u8?{urlencode(hls_params)}"
    return RedirectResponse(url=redirect_url, status_code=302)


@xtream_root_router.head("/{username}/{password}/{stream_id}.{ext}")
@xtream_root_router.get("/{username}/{password}/{stream_id}.{ext}")
async def live_stream_short(
    username: str,
    password: str,
    stream_id: str,
    ext: str,
    request: Request,
    proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
):
    """
    Short format live stream endpoint (without /live/ prefix).
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    stream_path = f"{actual_username}/{password}/{stream_id}.{ext}"
    upstream_url = urljoin(upstream_base, stream_path)

    logger.info(f"XC short live stream: {stream_path}")

    # For m3u8, redirect to HLS proxy
    if ext in ("m3u8", "m3u"):
        scheme = request.headers.get("x-forwarded-proto", request.url.scheme)
        host = request.headers.get("x-forwarded-host", request.headers.get("host", request.url.netloc))
        hls_params = {"d": upstream_url}
        if api_password:
            hls_params["api_password"] = api_password
        redirect_url = f"{scheme}://{host}/proxy/hls/manifest.m3u8?{urlencode(hls_params)}"
        return RedirectResponse(url=redirect_url, status_code=302)

    return await proxy_stream(request.method, upstream_url, proxy_headers)


# =============================================================================
# Stream Endpoints WITHOUT Extension (for players like IMPlayer)
# These handle URLs like /{username}/{password}/{stream_id} without .ts/.m3u8
# =============================================================================


@xtream_root_router.head("/live/{username}/{password}/{stream_id}")
@xtream_root_router.get("/live/{username}/{password}/{stream_id}")
async def live_stream_no_ext(
    username: str,
    password: str,
    stream_id: str,
    request: Request,
    proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
):
    """
    Live stream endpoint without extension (defaults to .ts).
    Some players like IMPlayer don't include the extension in stream URLs.
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    # Default to .ts format when no extension provided
    stream_path = f"live/{actual_username}/{password}/{stream_id}"
    upstream_url = urljoin(upstream_base, stream_path)

    logger.info(f"XC live stream (no ext): {stream_path}")

    return await proxy_stream(request.method, upstream_url, proxy_headers)


@xtream_root_router.head("/movie/{username}/{password}/{stream_id}")
@xtream_root_router.get("/movie/{username}/{password}/{stream_id}")
async def movie_stream_no_ext(
    username: str,
    password: str,
    stream_id: str,
    request: Request,
    proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
    transcode: bool = Query(False, description="Transcode to browser-compatible fMP4"),
    hls: bool = Query(False, description="Generate HLS VOD playlist for transcode (seekable)"),
    hls_init: bool = Query(False, description="Serve fMP4 init segment"),
    seg: int | None = Query(None, description="HLS segment number (informational)"),
    start_ms: float | None = Query(None, description="HLS segment start time in milliseconds"),
    end_ms: float | None = Query(None, description="HLS segment end time in milliseconds"),
    start: float | None = Query(None, description="Seek start time in seconds (transcode mode)"),
):
    """
    Movie stream endpoint without extension.
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    stream_path = f"movie/{actual_username}/{password}/{stream_id}"
    upstream_url = urljoin(upstream_base, stream_path)

    logger.info(f"XC movie stream (no ext): {stream_path}")

    if hls:
        return await _handle_xtream_hls_playlist(request, upstream_url, proxy_headers)
    if hls_init:
        return await _handle_xtream_hls_init(request, upstream_url, proxy_headers)
    if (start_ms is None) != (end_ms is None):
        raise HTTPException(status_code=400, detail="Both start_ms and end_ms are required for segment requests")
    if start_ms is not None and end_ms is not None:
        return await _handle_xtream_hls_segment(request, upstream_url, proxy_headers, start_ms, end_ms, seg)
    if transcode:
        return await _handle_xtream_transcode(request, upstream_url, proxy_headers, start)

    return await proxy_stream(request.method, upstream_url, proxy_headers)


@xtream_root_router.head("/series/{username}/{password}/{stream_id}")
@xtream_root_router.get("/series/{username}/{password}/{stream_id}")
async def series_stream_no_ext(
    username: str,
    password: str,
    stream_id: str,
    request: Request,
    proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
    transcode: bool = Query(False, description="Transcode to browser-compatible fMP4"),
    hls: bool = Query(False, description="Generate HLS VOD playlist for transcode (seekable)"),
    hls_init: bool = Query(False, description="Serve fMP4 init segment"),
    seg: int | None = Query(None, description="HLS segment number (informational)"),
    start_ms: float | None = Query(None, description="HLS segment start time in milliseconds"),
    end_ms: float | None = Query(None, description="HLS segment end time in milliseconds"),
    start: float | None = Query(None, description="Seek start time in seconds (transcode mode)"),
):
    """
    Series stream endpoint without extension.
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    stream_path = f"series/{actual_username}/{password}/{stream_id}"
    upstream_url = urljoin(upstream_base, stream_path)

    logger.info(f"XC series stream (no ext): {stream_path}")

    if hls:
        return await _handle_xtream_hls_playlist(request, upstream_url, proxy_headers)
    if hls_init:
        return await _handle_xtream_hls_init(request, upstream_url, proxy_headers)
    if (start_ms is None) != (end_ms is None):
        raise HTTPException(status_code=400, detail="Both start_ms and end_ms are required for segment requests")
    if start_ms is not None and end_ms is not None:
        return await _handle_xtream_hls_segment(request, upstream_url, proxy_headers, start_ms, end_ms, seg)
    if transcode:
        return await _handle_xtream_transcode(request, upstream_url, proxy_headers, start)

    return await proxy_stream(request.method, upstream_url, proxy_headers)


@xtream_root_router.head("/{username}/{password}/{stream_id}")
@xtream_root_router.get("/{username}/{password}/{stream_id}")
async def live_stream_short_no_ext(
    username: str,
    password: str,
    stream_id: str,
    request: Request,
    proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
):
    """
    Short format live stream endpoint without extension (without /live/ prefix).
    Some players like IMPlayer use this format without extension.
    """
    upstream_base, actual_username, api_password = parse_username_with_upstream(username)
    verify_xc_api_password(api_password)

    stream_path = f"{actual_username}/{password}/{stream_id}"
    upstream_url = urljoin(upstream_base, stream_path)

    logger.info(f"XC short live stream (no ext): {stream_path}")

    return await proxy_stream(request.method, upstream_url, proxy_headers)
