"""Apply script job completion callbacks — Redis live state then MySQL flush."""

from __future__ import annotations

import json
import logging
from datetime import datetime, timezone
from typing import Any, Optional

from server.application.services.script_jobs import consume_script_job
from server.domain.repositories import ScriptExecutionRepository
from server.infrastructure.redis import script_execution_store as ses

logger = logging.getLogger("nexus.script_job_callback")


def _aggregate_execution_status(
    server_ids: list[int], results: dict[str, Any]
) -> str:
    pending = 0
    failed = 0
    for sid in server_ids:
        r = results.get(str(sid), {})
        phase = r.get("phase", "done")
        if phase == "pending":
            pending += 1
            continue
        if r.get("exit_code", -1) != 0:
            failed += 1
    if pending:
        return "running"
    if failed == 0:
        return "completed"
    if failed == len(server_ids):
        return "failed"
    return "partial"


async def apply_script_job_callback(
    execution_repo: ScriptExecutionRepository,
    job_id: str,
    server_id: int,
    secret: str,
    exit_code: int,
    message: str = "",
    log_tail: Optional[str] = None,
    operator: str = "agent",
) -> Optional[dict[str, Any]]:
    job = await consume_script_job(job_id, server_id, secret)
    if not job:
        return None

    execution_id = int(job["execution_id"])
    view = await ses.get_execution_view(execution_repo, execution_id)
    if not view:
        logger.warning("Script callback for missing execution_id=%s", execution_id)
        return None

    results = dict(view.get("results") or {})
    entry: dict[str, Any] = {
        "phase": "done",
        "background": True,
        "job_id": job_id,
        "status": "completed" if exit_code == 0 else "failed",
        "exit_code": exit_code,
        "message": message or "",
        "completed_at": datetime.now(timezone.utc).isoformat(),
    }
    if log_tail:
        entry["log_tail"] = log_tail[:2000]
    prev = results.get(str(server_id), {})
    entry["stdout"] = prev.get("stdout", "") or entry.get("stdout", "")
    if prev.get("stderr"):
        entry["stderr"] = prev.get("stderr", "")
    results[str(server_id)] = {**prev, **entry}

    try:
        server_ids = json.loads(view["server_ids"]) if isinstance(view["server_ids"], str) else view.get("server_ids", [])
    except json.JSONDecodeError:
        server_ids = []

    status = _aggregate_execution_status(server_ids, results)
    terminal = status in ses.TERMINAL_STATUSES

    await ses.apply_update(
        execution_repo,
        execution_id,
        status=status,
        results=results,
        event_action="job_completed",
        event_detail=(
            f"子机 #{server_id} 回调 exit_code={exit_code}"
            + (f" — {message}" if message else "")
        ),
        operator=operator,
        terminal=terminal,
    )

    return {
        "execution_id": execution_id,
        "server_id": server_id,
        "status": status,
        "exit_code": exit_code,
    }
