import os

from flask import send_from_directory

from .app import *
from .app import routes as routes
from .app.routes.worker_routes import worker_bp
from .app.routes.phone_brick_routes import phone_brick_bp
from .app.routes.discord_routes import discord_bp


class ApiPrefixMiddleware:
    """Strip a leading /api from the request path, exactly like the public
    nginx does. With it, the app standalone (no proxy in front) accepts both
    the bare paths gunicorn has always served (/health, /v1/...) and the
    /api-prefixed paths every client uses (/api/health, /api/v1/...). This is
    what lets one process serve UI + API with no nginx at all."""

    def __init__(self, wsgi_app):
        self.wsgi_app = wsgi_app

    def __call__(self, environ, start_response):
        path = environ.get("PATH_INFO", "")
        if path == "/api" or path.startswith("/api/"):
            environ["PATH_INFO"] = path[len("/api"):] or "/"
        return self.wsgi_app(environ, start_response)


def _ui_dist_dir() -> str | None:
    """Where the built UI lives, if anywhere.

    HUGPY_UI_DIST (env or .env) wins; otherwise look for a sibling ui/dist
    next to the api tree (the repo layout: <root>/api/api + <root>/ui/dist).
    Returns None when there is no build — API-only mode, nothing changes."""
    explicit = os.environ.get("HUGPY_UI_DIST")
    if not explicit:
        try:
            from abstract_hugpy_dev.imports.src.standalone_utils import get_env_value
            explicit = get_env_value("HUGPY_UI_DIST")
        except Exception:
            explicit = None
    candidates = [explicit] if explicit else []
    here = os.path.dirname(os.path.abspath(__file__))   # <pkg>/flask_app
    # Packaged console: hugpy/console_dist ships inside the wheel, so a bare
    # `pip install hugpy && hugpy serve` includes the UI.
    candidates.append(os.path.join(os.path.dirname(here), "console_dist"))
    # Dev checkout layout: <tree>/{api/<pkg>/flask_app, ui/dist}. From `here`
    # (<pkg>/flask_app) the tree root is three parents up: flask_app -> <pkg> ->
    # api -> <tree>.
    tree_root = os.path.abspath(os.path.join(here, "..", "..", ".."))
    candidates.append(os.path.join(tree_root, "ui", "dist"))
    for cand in candidates:
        if cand and os.path.isfile(os.path.join(cand, "index.html")):
            return cand
    return None


def _mount_ui(app, dist_dir: str) -> None:
    """Serve the built SPA: real files from dist, everything else (deep links
    like /login) falls back to index.html. API rules are explicit routes, so
    Werkzeug always prefers them over this converter catch-all."""

    @app.route("/", defaults={"asset": ""})
    @app.route("/<path:asset>")
    def _hugpy_ui(asset):
        target = os.path.join(dist_dir, asset)
        if asset and os.path.isfile(target):
            return send_from_directory(dist_dir, asset)
        return send_from_directory(dist_dir, "index.html")


def get_hugpy_flask(name=None,allowed_origins=None,debug=False):
    name = name or "hugpy_flask"
    app = get_Flask_app(
        name=name,
        routes=routes,
        allowed_origins=allowed_origins,
        debug=debug
    )
    # Dual-mount the worker/model routes under /api as well.
    #
    # gunicorn serves these at /llm/... and /models; the /api prefix the worker
    # uses exists ONLY because the public nginx (hugpy.ai) strips it. A worker
    # that reaches central directly — e.g. over WireGuard at http://<wg-ip>:7002,
    # bypassing nginx — would 404 on its /api/llm/... calls. Registering the
    # blueprint again under /api makes /api/llm/workers/* and /api/llm/models/*
    # resolve on gunicorn itself, so the direct (proxy-less) route works
    # identically to the nginx route. The original /llm/... mount is untouched.
    try:
        app.register_blueprint(worker_bp, url_prefix="/api", name="worker_bp_api")
    except (ValueError, AssertionError):
        # Idempotent: already mounted on this app instance.
        pass

    # Same /api dual-mount for the phone-brick pool: phones register, heartbeat,
    # and fetch seeded images by reaching gunicorn directly over the VPN.
    try:
        app.register_blueprint(phone_brick_bp, url_prefix="/api", name="phone_brick_bp_api")
    except (ValueError, AssertionError):
        pass

    # Same /api dual-mount for Discord bindings: the hugpy bot reaches central
    # directly (resolve a model for a channel/user, drain the outbox) and may
    # bypass nginx, so these must resolve on gunicorn itself too.
    try:
        app.register_blueprint(discord_bp, url_prefix="/api", name="discord_bp_api")
    except (ValueError, AssertionError):
        pass

    # Standalone (distribution) mode: accept /api/* without a proxy, and serve
    # the built UI when one exists. Both are no-ops in the proxied dev/prod
    # topology (nginx already strips /api; webpack serves the UI).
    app.wsgi_app = ApiPrefixMiddleware(app.wsgi_app)
    dist_dir = _ui_dist_dir()
    if dist_dir:
        _mount_ui(app, dist_dir)
    return app
