"""Canonical-system routes — the GLOBAL area vocabulary, and the mapping of a
project's Systems onto it (milestone 307, decision note 3026).

Two shapes live here because they are two halves of one idea:

- `/api/canonical-systems` — the catalog itself. Readable by any signed-in
  user (it is shared vocabulary, not user data); writable only by an admin,
  since a global list anyone can extend stops being a shared list.
- the mapping endpoints — authorised by the PROJECT, because mapping writes a
  project's own System row. The service enforces both; these are thin wrappers.
"""
import logging

from quart import Blueprint, jsonify, request

from scribe.auth import admin_required, get_current_user_id, login_required
from scribe.routes.utils import not_found
from scribe.services import canonical_systems as canonical_svc
from scribe.services.projects import get_project_for_user

logger = logging.getLogger(__name__)

canonical_systems_bp = Blueprint("canonical_systems", __name__, url_prefix="/api")


@canonical_systems_bp.route("/canonical-systems", methods=["GET"])
@login_required
async def list_canonical_systems_route():
    entries = await canonical_svc.list_canonical_systems()
    return jsonify({"canonical_systems": [e.to_dict() for e in entries]})


@canonical_systems_bp.route("/canonical-systems", methods=["POST"])
@admin_required
async def create_canonical_system_route():
    uid = get_current_user_id()
    data = await request.get_json() or {}
    if not (data.get("name") or "").strip():
        return jsonify({"error": "name is required"}), 400
    entry = await canonical_svc.create_canonical_system(
        uid, data["name"], description=data.get("description"),
    )
    if entry is None:
        return jsonify({"error": "Permission denied"}), 403
    # The slug duplicate gate answers with the entry that already covers the
    # area rather than minting a second spelling of it — 409, not a silent
    # second row (the whole point of the table).
    if isinstance(entry, dict):
        return jsonify(entry), 409
    return jsonify(entry.to_dict()), 201


@canonical_systems_bp.route("/canonical-systems/<int:canonical_id>", methods=["PATCH"])
@admin_required
async def update_canonical_system_route(canonical_id: int):
    uid = get_current_user_id()
    data = await request.get_json() or {}
    fields = {k: v for k, v in data.items() if k in ("name", "description", "order_index")}
    entry = await canonical_svc.update_canonical_system(uid, canonical_id, **fields)
    if entry is None:
        return not_found("Canonical system")
    return jsonify(entry.to_dict())


@canonical_systems_bp.route(
    "/projects/<int:project_id>/canonical-proposals", methods=["GET"]
)
@login_required
async def propose_canonical_mappings_route(project_id: int):
    """Proposals only — this endpoint writes nothing. The PUT below applies one."""
    uid = get_current_user_id()
    if await get_project_for_user(uid, project_id) is None:
        return not_found("Project")
    return jsonify({"proposals": await canonical_svc.propose_mappings(uid, project_id)})


@canonical_systems_bp.route("/systems/<int:system_id>/canonical", methods=["PUT"])
@login_required
async def map_system_to_canonical_route(system_id: int):
    """Map or unmap one System. Body: {"canonical_id": <id>|null}.

    Sets that column and nothing else — no rename, no change to which records
    are tagged to the System.
    """
    uid = get_current_user_id()
    data = await request.get_json() or {}
    canonical_id = data.get("canonical_id")
    if canonical_id is not None and not isinstance(canonical_id, int):
        return jsonify({"error": "canonical_id must be an integer or null"}), 400
    system = await canonical_svc.set_system_canonical(uid, system_id, canonical_id)
    if system is None:
        return not_found("System or canonical system")
    return jsonify(system.to_dict())
