from __future__ import annotations

import logging
from http import HTTPStatus
from typing import Any, Mapping

from flask import Blueprint, jsonify, request

from .models import (
    ValidationError,
    validate_config_payload,
    validate_registration_payload,
    validate_status_payload,
)
from .scheduler import StatusScheduler
from .storage import Storage
from .util import to_iso, utcnow


def create_nodes_blueprint(storage: Storage, scheduler: StatusScheduler) -> Blueprint:
    bp = Blueprint("nodes", __name__)
    logger = logging.getLogger("argus.master.api")

    def _json_error(message: str, status: HTTPStatus, code: str) -> Any:
        response = jsonify({"error": message, "code": code})
        response.status_code = status
        return response

    @bp.errorhandler(ValidationError)
    def _handle_validation_error(err: ValidationError):
        return _json_error(str(err), HTTPStatus.BAD_REQUEST, "invalid_request")

    @bp.get("/nodes")
    def list_nodes():
        nodes = storage.list_nodes()
        return jsonify(nodes)

    @bp.get("/nodes/<node_id>")
    def get_node(node_id: str):
        node = storage.get_node(node_id)
        if node is None:
            return _json_error("Node not found", HTTPStatus.NOT_FOUND, "not_found")
        return jsonify(node)

    @bp.post("/nodes")
    def register_node():
        payload = _get_json()
        data = validate_registration_payload(payload)
        now = utcnow()
        now_iso = to_iso(now)
        node_id = data["id"]
        name = data["name"]
        node_type = data["type"]
        version = data["version"]
        meta = data["meta_data"]

        if node_id:
            # 携带 id 说明是重注册，需要校验名称一致性
            existing_row = storage.get_node_raw(node_id)
            if existing_row is None:
                return _json_error("Node not found", HTTPStatus.NOT_FOUND, "not_found")
            if existing_row["name"] != name:
                return _json_error(
                    "Node id and name mismatch during re-registration",
                    HTTPStatus.INTERNAL_SERVER_ERROR,
                    "id_name_mismatch",
                )
            updated = storage.update_node_meta(
                node_id,
                node_type=node_type,
                version=version,
                meta_data=meta,
                last_updated_iso=now_iso,
            )
            scheduler.trigger_nodes_json_refresh()
            return jsonify(updated), HTTPStatus.OK

        # No id provided → search by name
        existing_by_name = storage.get_node_by_name(name)
        if existing_by_name:
            # 同名节点已存在，视为无 id 重注册
            updated = storage.update_node_meta(
                existing_by_name["id"],
                node_type=node_type,
                version=version,
                meta_data=meta,
                last_updated_iso=now_iso,
            )
            scheduler.trigger_nodes_json_refresh()
            return jsonify(updated), HTTPStatus.OK

        new_id = storage.allocate_node_id()
        created = storage.create_node(
            new_id,
            name,
            node_type,
            version,
            meta,
            status="initialized",
            register_time_iso=now_iso,
            last_updated_iso=now_iso,
        )
        scheduler.trigger_nodes_json_refresh()
        return jsonify(created), HTTPStatus.CREATED

    @bp.put("/nodes/<node_id>/config")
    def update_node_config(node_id: str):
        payload = _get_json()
        updates = validate_config_payload(payload)
        try:
            updated = storage.update_config_and_labels(
                node_id,
                config=updates.get("config"),
                labels=updates.get("label"),
            )
        except KeyError:
            return _json_error("Node not found", HTTPStatus.NOT_FOUND, "not_found")

        if "label" in updates:
            scheduler.trigger_nodes_json_refresh()
        return jsonify(updated)

    @bp.get("/nodes/statistics")
    def node_statistics():
        stats = storage.get_statistics()
        return jsonify(stats)

    @bp.put("/nodes/<node_id>/status")
    def update_status(node_id: str):
        payload = _get_json()
        data = validate_status_payload(payload)
        try:
            # master 负责写入 last_report，状态由调度器计算
            updated = storage.update_last_report(
                node_id,
                server_timestamp_iso=to_iso(utcnow()),
                agent_timestamp_iso=data["timestamp"],
                health=data["health"],
            )
        except KeyError:
            return _json_error("Node not found", HTTPStatus.NOT_FOUND, "not_found")

        scheduler.trigger_nodes_json_refresh()
        return jsonify(updated)

    return bp


def _get_json() -> Mapping[str, Any]:
    data = request.get_json(silent=True)
    if data is None:
        raise ValidationError("Request body must be valid JSON")
    if not isinstance(data, Mapping):
        raise ValidationError("Request body must be a JSON object")
    return data
