import logging
import subprocess

import click
from aiohttp import web

from envied.core import binaries
from envied.core.api import cors_middleware, setup_routes, setup_swagger
from envied.core.config import config
from envied.core.constants import context_settings


@click.command(
    short_help="Serve your Local Widevine/PlayReady Devices and REST API for Remote Access.",
    context_settings=context_settings,
)
@click.option("-h", "--host", type=str, default="127.0.0.1", help="Host to serve from.")
@click.option("-p", "--port", type=int, default=8786, help="Port to serve from.")
@click.option("--caddy", is_flag=True, default=False, help="Also serve with Caddy.")
@click.option(
    "--api-only", is_flag=True, default=False, help="Serve only the REST API, not pywidevine/pyplayready CDM."
)
@click.option("--no-widevine", is_flag=True, default=False, help="Disable Widevine CDM endpoints.")
@click.option("--no-playready", is_flag=True, default=False, help="Disable PlayReady CDM endpoints.")
@click.option("--no-key", is_flag=True, default=False, help="Disable API key authentication (allows all requests).")
@click.option(
    "--debug-api",
    is_flag=True,
    default=False,
    help="Include technical debug information (tracebacks, stderr) in API error responses.",
)
@click.option(
    "--debug",
    is_flag=True,
    default=False,
    help="Enable debug logging for API operations.",
)
def serve(
    host: str,
    port: int,
    caddy: bool,
    api_only: bool,
    no_widevine: bool,
    no_playready: bool,
    no_key: bool,
    debug_api: bool,
    debug: bool,
) -> None:
    """
    Serve your Local Widevine and PlayReady Devices and REST API for Remote Access.

    \b
    CDM ENDPOINTS:
    - Widevine: /{device}/open, /{device}/close/{session_id}, etc.
    - PlayReady: /playready/{device}/open, /playready/{device}/close/{session_id}, etc.

    \b
    You may serve with Caddy at the same time with --caddy. You can use Caddy
    as a reverse-proxy to serve with HTTPS. The config used will be the Caddyfile
    next to the unshackle config.

    \b
    DEVICE CONFIGURATION:
    WVD files are auto-loaded from the WVDs directory, PRD files from the PRDs directory.
    Configure user access in envied.yaml:

    \b
    serve:
      api_secret: "your-api-secret"
      users:
        your-secret-key:
          devices: ["device_name"]  # Widevine devices
          playready_devices: ["device_name"]  # PlayReady devices
          username: user
    """
    from pyplayready.remote import serve as pyplayready_serve
    from pywidevine import serve as pywidevine_serve

    log = logging.getLogger("serve")

    if debug:
        logging.basicConfig(level=logging.DEBUG, format="%(name)s - %(levelname)s - %(message)s")
        log.info("Debug logging enabled for API operations")
    else:
        logging.getLogger("api").setLevel(logging.WARNING)
        logging.getLogger("api.remote").setLevel(logging.WARNING)

    if not no_key:
        api_secret = config.serve.get("api_secret")
        if not api_secret:
            raise click.ClickException(
                "API secret key is not configured. Please add 'api_secret' to the 'serve' section in your config."
            )
    else:
        api_secret = None
        log.warning("Running with --no-key: Authentication is DISABLED for all API endpoints!")

    if debug_api:
        log.warning("Running with --debug-api: Error responses will include technical debug information!")

    if api_only and (no_widevine or no_playready):
        raise click.ClickException("Cannot use --api-only with --no-widevine or --no-playready.")

    if caddy:
        if not binaries.Caddy:
            raise click.ClickException('Caddy executable "caddy" not found but is required for --caddy.')
        caddy_p = subprocess.Popen(
            [binaries.Caddy, "run", "--config", str(config.directories.user_configs / "Caddyfile")]
        )
    else:
        caddy_p = None

    try:
        if not config.serve.get("devices"):
            config.serve["devices"] = []
        config.serve["devices"].extend(list(config.directories.wvds.glob("*.wvd")))

        if not config.serve.get("playready_devices"):
            config.serve["playready_devices"] = []
        config.serve["playready_devices"].extend(list(config.directories.prds.glob("*.prd")))

        if api_only:
            log.info("Starting REST API server (pywidevine/pyplayready CDM disabled)")
            if no_key:
                app = web.Application(middlewares=[cors_middleware])
                app["config"] = {"users": {}}
            else:
                app = web.Application(middlewares=[cors_middleware, pywidevine_serve.authentication])
                app["config"] = {"users": {api_secret: {"devices": [], "username": "api_user"}}}
            app["debug_api"] = debug_api
            setup_routes(app)
            setup_swagger(app)
            log.info(f"REST API endpoints available at http://{host}:{port}/api/")
            log.info(f"Swagger UI available at http://{host}:{port}/api/docs/")
            log.info("(Press CTRL+C to quit)")
            web.run_app(app, host=host, port=port, print=None)
        else:
            serve_widevine = not no_widevine
            serve_playready = not no_playready

            serve_config = dict(config.serve)
            wvd_devices = serve_config.get("devices", []) if serve_widevine else []
            prd_devices = serve_config.get("playready_devices", []) if serve_playready else []

            cdm_parts = []
            if serve_widevine:
                cdm_parts.append("pywidevine CDM")
            if serve_playready:
                cdm_parts.append("pyplayready CDM")
            log.info(f"Starting integrated server ({' + '.join(cdm_parts)} + REST API)")

            wvd_device_names = [d.stem if hasattr(d, "stem") else str(d) for d in wvd_devices]
            prd_device_names = [d.stem if hasattr(d, "stem") else str(d) for d in prd_devices]

            if not serve_config.get("users") or not isinstance(serve_config["users"], dict):
                serve_config["users"] = {}

            if not no_key and api_secret not in serve_config["users"]:
                serve_config["users"][api_secret] = {
                    "devices": wvd_device_names,
                    "playready_devices": prd_device_names,
                    "username": "api_user",
                }

            for user_key, user_config in serve_config["users"].items():
                if "playready_devices" not in user_config:
                    # Require explicit PlayReady device access per user (default: no access).
                    user_config["playready_devices"] = []
                    log.warning(
                        f'User "{user_key}" has no "playready_devices" configured; PlayReady access disabled for this user. '
                        f"Available PlayReady devices: {prd_device_names}"
                    )

            def create_serve_authentication(serve_playready_flag: bool):
                @web.middleware
                async def serve_authentication(request: web.Request, handler) -> web.Response:
                    if serve_playready_flag and request.path in ("/playready", "/playready/"):
                        response = await handler(request)
                    else:
                        response = await pywidevine_serve.authentication(request, handler)

                    if serve_playready_flag and request.path.startswith("/playready"):
                        from pyplayready import __version__ as pyplayready_version
                        response.headers["Server"] = f"https://git.gay/ready-dl/pyplayready serve v{pyplayready_version}"

                    return response
                return serve_authentication

            if no_key:
                app = web.Application(middlewares=[cors_middleware])
            else:
                serve_auth = create_serve_authentication(serve_playready and bool(prd_devices))
                app = web.Application(middlewares=[cors_middleware, serve_auth])

            app["config"] = serve_config
            app["debug_api"] = debug_api

            if serve_widevine:
                app.on_startup.append(pywidevine_serve._startup)
                app.on_cleanup.append(pywidevine_serve._cleanup)
                app.add_routes(pywidevine_serve.routes)

            if serve_playready and prd_devices:
                if no_key:
                    playready_app = web.Application()
                else:
                    playready_app = web.Application(middlewares=[pyplayready_serve.authentication])

                # PlayReady subapp config maps playready_devices to "devices" for pyplayready compatibility
                playready_config = {
                    "devices": prd_devices,
                    "users": {
                        user_key: {
                            "devices": user_cfg.get("playready_devices", []),
                            "username": user_cfg.get("username", "user"),
                        }
                        for user_key, user_cfg in serve_config["users"].items()
                    }
                    if not no_key
                    else {},
                }
                playready_app["config"] = playready_config
                playready_app.on_startup.append(pyplayready_serve._startup)
                playready_app.on_cleanup.append(pyplayready_serve._cleanup)
                playready_app.add_routes(pyplayready_serve.routes)

                async def playready_ping(_: web.Request) -> web.Response:
                    from pyplayready import __version__ as pyplayready_version
                    response = web.json_response({"message": "OK"})
                    response.headers["Server"] = f"https://git.gay/ready-dl/pyplayready serve v{pyplayready_version}"
                    return response

                app.router.add_route("*", "/playready", playready_ping)

                app.add_subapp("/playready", playready_app)
                log.info(f"PlayReady CDM endpoints available at http://{host}:{port}/playready/")
            elif serve_playready:
                log.info("No PlayReady devices found, skipping PlayReady CDM endpoints")

            setup_routes(app)
            setup_swagger(app)

            if serve_widevine:
                log.info(f"Widevine CDM endpoints available at http://{host}:{port}/{{device}}/open")
            log.info(f"REST API endpoints available at http://{host}:{port}/api/")
            log.info(f"Swagger UI available at http://{host}:{port}/api/docs/")
            log.info("(Press CTRL+C to quit)")
            web.run_app(app, host=host, port=port, print=None)
    finally:
        if caddy_p:
            caddy_p.kill()
