"""Update endpoints for the controller API."""
from __future__ import annotations

import logging
from pathlib import Path
from typing import Optional

from fastapi import APIRouter, File, Form, HTTPException, UploadFile
from fastapi.responses import JSONResponse

from mesh.models import StatusResponse
from mesh.updates.models import UpdateHistory, UpdateInfo, UpdateStatus

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/updates", tags=["updates"])

# Wired by start_controller.py
_update_manager = None
_update_distributor = None
_coordinator = None


def wire_updates(update_manager, update_distributor=None, coordinator=None):
    """Wire in update components."""
    global _update_manager, _update_distributor, _coordinator
    _update_manager = update_manager
    _update_distributor = update_distributor
    _coordinator = coordinator


# ── Controller self-update endpoints ─────────────────────────────

@router.get("/check")
async def check_for_updates(channel: str = "stable") -> dict:
    """Check if a newer version is available for the controller."""
    if _update_manager is None:
        raise HTTPException(503, "Update manager not initialized")

    info = _update_manager.check_for_updates(channel=channel)
    if info is None:
        return {
            "status": "up_to_date",
            "current_version": _update_manager.current_version,
        }

    return {
        "status": "update_available",
        "current_version": _update_manager.current_version,
        "update": info.model_dump(mode="json"),
    }


@router.post("/apply")
async def apply_update(
    package: Optional[UploadFile] = File(None),
    package_path: Optional[str] = Form(None),
    sha256: str = Form(""),
    restart: bool = Form(True),
) -> StatusResponse:
    """Apply an update to the controller.

    Accepts either:
    - A multipart file upload (package)
    - A local file path (package_path)
    """
    if _update_manager is None:
        raise HTTPException(503, "Update manager not initialized")

    # Determine the package path
    if package is not None:
        # Save uploaded file to temp
        import tempfile
        tmp = Path(tempfile.mkdtemp(prefix="mesh-update-"))
        pkg_path = tmp / package.filename
        content = await package.read()
        pkg_path.write_bytes(content)
    elif package_path:
        pkg_path = Path(package_path)
        if not pkg_path.exists():
            raise HTTPException(404, f"Package not found: {package_path}")
    else:
        raise HTTPException(400, "Either package file or package_path required")

    # Verify if SHA-256 provided
    if sha256:
        if not _update_manager.verify_update(pkg_path, sha256):
            return StatusResponse(
                status="error",
                message="SHA-256 verification failed",
            )

    # Apply
    status = _update_manager.apply_update(pkg_path, restart=restart)

    if status == UpdateStatus.COMPLETE:
        return StatusResponse(
            status="ok",
            message=f"Update applied successfully (v{_update_manager.current_version})",
            data={"version": _update_manager.current_version},
        )
    elif status == UpdateStatus.ROLLED_BACK:
        return StatusResponse(
            status="error",
            message="Update failed and was rolled back",
            data={"version": _update_manager.current_version},
        )
    else:
        return StatusResponse(
            status="error",
            message="Update failed",
        )


@router.get("/history")
async def update_history() -> dict:
    """List past updates with timestamps and status."""
    if _update_manager is None:
        raise HTTPException(503, "Update manager not initialized")

    history = _update_manager.history
    return {
        "current_version": history.current_version,
        "entries": [e.model_dump(mode="json") for e in history.entries],
    }


@router.post("/rollback")
async def rollback() -> StatusResponse:
    """Rollback the controller to the previous version."""
    if _update_manager is None:
        raise HTTPException(503, "Update manager not initialized")

    status = _update_manager.rollback()

    if status == UpdateStatus.ROLLED_BACK:
        return StatusResponse(
            status="ok",
            message=f"Rolled back to v{_update_manager.current_version}",
            data={"version": _update_manager.current_version},
        )
    else:
        return StatusResponse(
            status="error",
            message="Rollback failed — no backup available",
        )


# ── Agent update endpoints ───────────────────────────────────────

@router.post("/nodes/{node_id}")
async def update_node(
    node_id: str,
    package: Optional[UploadFile] = File(None),
    package_path: Optional[str] = Form(None),
) -> StatusResponse:
    """Push an update to a specific agent node."""
    if _update_distributor is None:
        raise HTTPException(503, "Update distributor not initialized")

    # Determine package
    if package is not None:
        import tempfile
        tmp = Path(tempfile.mkdtemp(prefix="mesh-update-"))
        pkg_path = tmp / package.filename
        content = await package.read()
        pkg_path.write_bytes(content)
    elif package_path:
        pkg_path = Path(package_path)
        if not pkg_path.exists():
            raise HTTPException(404, f"Package not found: {package_path}")
    else:
        raise HTTPException(400, "Either package file or package_path required")

    # Create agent-specific package (strips controller code)
    agent_pkg = _update_distributor.create_agent_package(pkg_path)

    status = await _update_distributor.push_to_agent(node_id, agent_pkg)

    if status == UpdateStatus.COMPLETE:
        return StatusResponse(
            status="ok",
            message=f"Node {node_id} updated successfully",
        )
    else:
        return StatusResponse(
            status="error",
            message=f"Failed to update node {node_id}",
        )


@router.post("/nodes-all")
async def update_all_nodes(
    package: Optional[UploadFile] = File(None),
    package_path: Optional[str] = Form(None),
    rolling: bool = Form(True),
) -> StatusResponse:
    """Push an update to all online agents."""
    if _update_distributor is None:
        raise HTTPException(503, "Update distributor not initialized")

    # Determine package
    if package is not None:
        import tempfile
        tmp = Path(tempfile.mkdtemp(prefix="mesh-update-"))
        pkg_path = tmp / package.filename
        content = await package.read()
        pkg_path.write_bytes(content)
    elif package_path:
        pkg_path = Path(package_path)
        if not pkg_path.exists():
            raise HTTPException(404, f"Package not found: {package_path}")
    else:
        raise HTTPException(400, "Either package file or package_path required")

    # Create agent-specific package
    agent_pkg = _update_distributor.create_agent_package(pkg_path)

    results = await _update_distributor.push_to_all(agent_pkg, rolling=rolling)

    success = sum(1 for s in results.values() if s == UpdateStatus.COMPLETE)
    total = len(results)

    return StatusResponse(
        status="ok" if success == total else "partial",
        message=f"Updated {success}/{total} nodes",
        data={
            "results": {nid: s.value for nid, s in results.items()},
        },
    )
