import flask

from abstra_internals.cloud_api import save_editor_auth_token_to_file
from abstra_internals.controllers.main import MainController
from abstra_internals.environment import EDITOR_MODE, EXTERNAL_PLAYER_URL, PROJECT_ID
from abstra_internals.logger import AbstraLogger
from abstra_internals.repositories.project.project import PathConflictError
from abstra_internals.server.routes import access_control as ac_router
from abstra_internals.server.routes import ai as ai_router
from abstra_internals.server.routes import assets as assets_router
from abstra_internals.server.routes import code_markers as code_markers_router
from abstra_internals.server.routes import codebase as codebase_router
from abstra_internals.server.routes import connectors as connectors_router
from abstra_internals.server.routes import deploy as deploy_router
from abstra_internals.server.routes import editor_status as editor_status_router
from abstra_internals.server.routes import env_vars as envvars_router
from abstra_internals.server.routes import executions as executions_router
from abstra_internals.server.routes import file_history as file_history_router
from abstra_internals.server.routes import forms as forms_router
from abstra_internals.server.routes import git as git_router
from abstra_internals.server.routes import hooks as hooks_router
from abstra_internals.server.routes import jobs as jobs_router
from abstra_internals.server.routes import language_server as language_server_router
from abstra_internals.server.routes import linters as linters_router
from abstra_internals.server.routes import login as login_router
from abstra_internals.server.routes import logs as logs_router
from abstra_internals.server.routes import mcp as mcp_router
from abstra_internals.server.routes import modules as modules_router
from abstra_internals.server.routes import pages as pages_router
from abstra_internals.server.routes import presence as presence_router
from abstra_internals.server.routes import requirements as requirements_router
from abstra_internals.server.routes import roles as roles_router
from abstra_internals.server.routes import scripts as scripts_router
from abstra_internals.server.routes import stages as stages_router
from abstra_internals.server.routes import stdio as stdio_router
from abstra_internals.server.routes import tasks as tasks_router
from abstra_internals.server.routes import web_editor as web_editor_router
from abstra_internals.server.routes import workflows as workflows_router
from abstra_internals.server.routes import workspace as workspace_router
from abstra_internals.server.utils import send_from_dist
from abstra_internals.services.jwt import decode_jwt
from abstra_internals.usage import editor_usage


def _get_api_bp(controller: MainController):
    bp = flask.Blueprint("editor_api", __name__)

    @bp.errorhandler(PathConflictError)
    def _handle_path_conflict(e: PathConflictError):
        return flask.make_response({"error": "path_conflict", "message": str(e)}, 409)

    mcp_bp = mcp_router.get_editor_bp(controller)
    bp.register_blueprint(mcp_bp, url_prefix="/mcp")

    workspace_bp = workspace_router.get_editor_bp(controller)
    bp.register_blueprint(workspace_bp, url_prefix="/workspace")

    forms_bp = forms_router.get_editor_bp(controller)
    bp.register_blueprint(forms_bp, url_prefix="/forms")

    hooks_bp = hooks_router.get_editor_bp(controller)
    bp.register_blueprint(hooks_bp, url_prefix="/hooks")

    pages_bp = pages_router.get_editor_bp(controller)
    bp.register_blueprint(pages_bp, url_prefix="/pages")

    files_bp = codebase_router.get_editor_bp(controller.repositories)
    bp.register_blueprint(files_bp, url_prefix="/codebase")

    # Presence (who is currently editing) historically lived under /locks. The
    # lock feature is gone; the presence path stays here until it moves to
    # cloud-api with the rest of the editor API.
    presence_bp = presence_router.get_editor_bp(controller.repositories)
    bp.register_blueprint(presence_bp, url_prefix="/locks")

    jobs_bp = jobs_router.get_editor_bp(controller)
    bp.register_blueprint(jobs_bp, url_prefix="/jobs")

    scripts_bp = scripts_router.get_editor_bp(controller)
    bp.register_blueprint(scripts_bp, url_prefix="/scripts")

    stages_bp = stages_router.get_editor_bp(controller)
    bp.register_blueprint(stages_bp, url_prefix="/stages")

    executions_bp = executions_router.get_editor_bp(controller)
    bp.register_blueprint(executions_bp, url_prefix="/executions")

    logs_bp = logs_router.get_editor_bp(controller)
    bp.register_blueprint(logs_bp, url_prefix="/logs")

    workflows_bp = workflows_router.get_editor_bp(controller)
    bp.register_blueprint(workflows_bp, url_prefix="/workflows")

    requirements_bp = requirements_router.get_editor_bp(controller)
    bp.register_blueprint(requirements_bp, url_prefix="/requirements")

    login_bp = login_router.get_editor_bp(controller)
    bp.register_blueprint(login_bp, url_prefix="/login")

    linters_bp = linters_router.get_editor_bp(controller)
    bp.register_blueprint(linters_bp, url_prefix="/linters")

    editor_status_bp = editor_status_router.get_editor_bp()
    bp.register_blueprint(editor_status_bp, url_prefix="/editor-status")

    ai_bp = ai_router.get_editor_bp(controller)
    bp.register_blueprint(ai_bp, url_prefix="/ai")

    assets_bp = assets_router.get_editor_bp(controller)
    bp.register_blueprint(assets_bp, url_prefix="/assets")

    envvars_bp = envvars_router.get_editor_bp(controller)
    bp.register_blueprint(envvars_bp, url_prefix="/env-vars")

    language_server_bp = language_server_router.get_editor_bp()
    bp.register_blueprint(language_server_bp, url_prefix="/language-server")

    roles_bp = roles_router.get_editor_bp(controller)
    bp.register_blueprint(roles_bp, url_prefix="/roles")

    access_control_bp = ac_router.get_editor_bp()
    bp.register_blueprint(access_control_bp, url_prefix="/access-control")

    stdio_bp = stdio_router.get_editor_bp(controller)
    bp.register_blueprint(stdio_bp, url_prefix="/stdio")

    tasks_bp = tasks_router.get_editor_bp(controller)
    bp.register_blueprint(tasks_bp, url_prefix="/tasks")

    web_editor_bp = web_editor_router.get_web_editor_bp(controller)
    bp.register_blueprint(web_editor_bp, url_prefix="/web-editor")

    git_bp = git_router.get_editor_bp(controller)
    bp.register_blueprint(git_bp, url_prefix="/git")

    file_history_bp = file_history_router.get_editor_bp(controller)
    bp.register_blueprint(file_history_bp, url_prefix="/file-history")

    modules_bp = modules_router.get_editor_bp(controller)
    bp.register_blueprint(modules_bp, url_prefix="/modules")

    code_markers_bp = code_markers_router.get_editor_bp(controller)
    bp.register_blueprint(code_markers_bp, url_prefix="/markers")

    connectors_bp = connectors_router.get_editor_bp()
    bp.register_blueprint(connectors_bp, url_prefix="/connectors")

    deploy_bp = deploy_router.get_editor_bp(controller)
    bp.register_blueprint(deploy_bp, url_prefix="/deploy")

    @bp.get("/player-url")
    def _player_url():
        """Player preview URL for the editor UI.

        With an external (cloud-api-served) player, the preview host is a
        different origin, so the URL bounces through its set-cookie endpoint
        carrying this session's editor_auth token.
        """
        from urllib.parse import quote

        path = flask.request.args.get("path", "/")
        if not path.startswith("/") or path.startswith("//"):
            path = "/"

        if EDITOR_MODE == "web" and EXTERNAL_PLAYER_URL:
            token = flask.request.cookies.get("editor_auth") or ""
            url = (
                f"{EXTERNAL_PLAYER_URL}/_web-editor/auth/set-cookie"
                f"?token={quote(token)}&redirect={quote(path)}"
            )
            return {"url": url, "external": True}

        return {"url": path, "external": False}

    return bp


def get_editor_bp(controller: MainController):
    bp = flask.Blueprint("editor", __name__)

    api_bp = _get_api_bp(controller)
    bp.register_blueprint(api_bp, url_prefix="/api")

    @bp.get("/")
    @editor_usage
    def _spa_index():
        return send_from_dist("editor.html", "editor.html")

    @bp.get("/<path:filename>")
    def _spa(filename: str):
        # API URLs that fell through routing (e.g. double slashes in
        # /api/codebase/files//abs/path) must 404 instead of being swallowed
        # by the SPA fallback, which would serve editor.html to fetch/embed.
        if filename == "api" or filename.startswith("api/"):
            flask.abort(404)
        return send_from_dist(filename, "editor.html")

    return bp


def set_editor_auth_cookie(response: flask.Response, token: str) -> None:
    response.set_cookie(
        "editor_auth",
        token,
        max_age=60 * 60 * 24 * 7,  # Expires in 7 day (in seconds)
        httponly=True,  # Makes it inaccessible to JavaScript
        secure=True,  # Ensures it's sent over HTTPS
        samesite="Lax",  # Protects against CSRF
    )


def _safe_redirect_path(path) -> str:
    # Only same-origin relative paths — "//host" would be an open redirect.
    if isinstance(path, str) and path.startswith("/") and not path.startswith("//"):
        return path
    return "/_editor/"


def get_editor_auth_bp():
    bp = flask.Blueprint("editor_auth", __name__)

    @bp.get("/set-cookie")
    def _set_cookie():
        token = flask.request.args.get("token")

        if not token:
            AbstraLogger.capture_message(
                "Editor auth token not provided in request",
            )
            return flask.make_response({"ok": False, "error": "No token provided"}, 401)

        if not decode_jwt(token, aud=f"web-editor-{PROJECT_ID}"):
            AbstraLogger.capture_message(
                "Editor auth token is invalid",
            )
            return flask.make_response({"ok": False, "error": "Invalid token"}, 401)

        response = flask.make_response({"ok": True})

        # The console forwards an optional `redirect` param so an expired
        # session lands back on the page the user was at.
        response.headers["Location"] = _safe_redirect_path(
            flask.request.args.get("redirect")
        )
        response.status_code = 302

        save_editor_auth_token_to_file(token)

        set_editor_auth_cookie(response, token)
        return response

    return bp
