"""User settings + integrations (SearXNG status).

Chat-model picker endpoints (/models), KV-cache priming, and journal-schedule
hooks were removed in Phase 8 alongside the chat/journal subsystems.
"""
import logging

from quart import Blueprint, jsonify, request

from scribe.auth import login_required, get_current_user_id
from scribe.config import Config
from scribe.services.settings import (
    SECRET_MASK, delete_setting, get_all_settings, get_setting, set_settings_batch,
)

logger = logging.getLogger(__name__)


settings_bp = Blueprint("settings", __name__, url_prefix="/api/settings")

# Keys whose values are credentials. The admin endpoints that own them mask on
# read and skip the mask on write; this generic KV surface has to apply the
# same treatment, or it silently un-masks what those endpoints masked — the
# rows live on the admin's own user_id, so the plain GET returned them raw.
# (forge_token left with 0078: forge credentials are keyring rows now, #2778.)
_SECRET_KEYS = frozenset({"smtp_password", "forge_webhook_secret"})


def _masked(settings: dict) -> dict:
    return {
        k: (SECRET_MASK if k in _SECRET_KEYS and v else v)
        for k, v in settings.items()
    }


@settings_bp.route("", methods=["GET"])
@login_required
async def get_settings_route():
    uid = get_current_user_id()
    settings = await get_all_settings(uid)
    return jsonify(_masked(settings))


@settings_bp.route("", methods=["PUT"])
@login_required
async def update_settings_route():
    uid = get_current_user_id()
    data = await request.get_json()
    if not isinstance(data, dict):
        return jsonify({"error": "Expected a JSON object"}), 400

    to_save = {}
    for k, v in data.items():
        str_v = str(v)
        # A masked secret round-tripping through a client is "unchanged", not
        # a request to store the mask over the real credential.
        if k in _SECRET_KEYS and str_v == SECRET_MASK:
            continue
        if not str_v:
            await delete_setting(uid, k)
        else:
            to_save[k] = str_v

    if to_save:
        await set_settings_batch(uid, to_save)

    settings = await get_all_settings(uid)
    return jsonify(_masked(settings))


@settings_bp.route("/search", methods=["GET"])
@login_required
async def test_search():
    """Report SearXNG configuration status (used by the Integrations tab)."""
    if not Config.searxng_enabled():
        return jsonify({"configured": False, "results": [], "searxng_url": ""})
    return jsonify({"configured": True, "results": [], "searxng_url": Config.SEARXNG_URL})


# --- forge connections (#2778) ------------------------------------------------
# The user's keyring: read-only forge credentials, one per host, resolved by
# repo host for every server-side forge read on the user's projects. Strictly
# own-rows — a connection is a credential, and there is no admin view of
# another user's keyring. Tokens never leave the server: the model's to_dict
# omits them, and the routes never echo the submitted value back.


@settings_bp.route("/forge-connections", methods=["GET"])
@login_required
async def list_forge_connections_route():
    from scribe.services.forge import FORGE_KINDS
    from scribe.services.forge_connections import list_connections

    uid = get_current_user_id()
    rows = await list_connections(uid)
    return jsonify({
        "connections": [r.to_dict() for r in rows],
        "kinds": list(FORGE_KINDS),
    })


@settings_bp.route("/forge-connections", methods=["POST"])
@login_required
async def create_forge_connection_route():
    from scribe.services.forge_connections import create_connection

    uid = get_current_user_id()
    data = await request.get_json() or {}
    try:
        row = await create_connection(
            uid,
            kind=str(data.get("kind", "")),
            base_url=str(data.get("base_url", "")),
            token=str(data.get("token", "")),
        )
    except ValueError as exc:
        return jsonify({"error": str(exc)}), 400
    return jsonify(row.to_dict()), 201


@settings_bp.route("/forge-connections/<int:connection_id>", methods=["PUT"])
@login_required
async def update_forge_connection_route(connection_id: int):
    from scribe.services.forge_connections import update_connection

    uid = get_current_user_id()
    data = await request.get_json() or {}
    token = str(data.get("token", ""))
    # The mask coming back means "unchanged" — the form round-trips what the
    # list showed, and storing the mask would silently break the connection.
    if token == SECRET_MASK:
        token = ""
    try:
        row = await update_connection(
            uid, connection_id,
            kind=str(data.get("kind", "")),
            base_url=str(data.get("base_url", "")),
            token=token,
        )
    except ValueError as exc:
        return jsonify({"error": str(exc)}), 400
    if row is None:
        return jsonify({"error": "Connection not found"}), 404
    return jsonify(row.to_dict())


@settings_bp.route("/forge-connections/<int:connection_id>", methods=["DELETE"])
@login_required
async def delete_forge_connection_route(connection_id: int):
    from scribe.services.forge_connections import delete_connection

    uid = get_current_user_id()
    if not await delete_connection(uid, connection_id):
        return jsonify({"error": "Connection not found"}), 404
    return "", 204


@settings_bp.route("/forge-connections/<int:connection_id>/test", methods=["POST"])
@login_required
async def test_forge_connection_route(connection_id: int):
    """Probe the SAVED connection: reachability and token acceptance in one
    press, so a misconfiguration is visible now rather than as silent
    fallbacks later (#2663's lesson, applied per keyring row)."""
    from scribe.services.forge import ForgeError, build_adapter
    from scribe.services.forge_connections import get_connection

    uid = get_current_user_id()
    row = await get_connection(uid, connection_id)
    if row is None:
        return jsonify({"error": "Connection not found"}), 404
    adapter = build_adapter(row.kind, row.base_url, row.token)
    if adapter is None:
        return jsonify({"error": "Connection is not usable — check kind and base URL"}), 400
    try:
        return jsonify(await adapter.check())
    except ForgeError as exc:
        return jsonify({"error": str(exc)}), 502
