"""Pembuatan, penandatanganan, dan pengiriman perintah ke agent."""
from __future__ import annotations

from datetime import timedelta

from sqlalchemy.orm import Session

from ..config import settings
from ..models import Computer, ComputerCommand, utcnow
from ..security import build_signed_command
from ..websocket.manager import hub
from . import audit
from .settings_service import get_setting
from .wol import send_wol

PERMISSION_FOR = {
    "shutdown": "power", "restart": "power", "logout": "power", "abort_shutdown": "power", "wake": "power",
    "lock": "lock", "unlock": "lock", "lock_workstation": "lock", "message": "message", "refresh_policy": "lock",
    "close_browser": "browser", "kill_process": "apps", "release": "admin",
}
RISKY = {"shutdown", "restart", "logout", "close_browser", "kill_process", "release"}
ONLINE_ONLY = set(PERMISSION_FOR) - {"wake"}


def validate_params(action: str, params: dict) -> dict:
    if action == "message":
        text = str(params.get("text", "")).strip()
        if not text or len(text) > 500:
            raise ValueError("Pesan wajib diisi (maks 500 karakter)")
        return {"text": text, "seconds": max(3, min(int(params.get("seconds", 15)), 300)), "from": str(params.get("from", ""))[:80]}
    if action in ("shutdown", "restart"):
        return {"delay": max(10, min(int(params.get("delay", 30)), 600))}
    if action == "kill_process":
        name = str(params.get("name", "")).strip()
        if not name or not name.lower().endswith(".exe") or any(c in name for c in '\\/:*?"<>|'):
            raise ValueError("Nama proses harus berupa nama berkas .exe")
        return {"name": name}
    return {}


def execute(db: Session, comp: Computer, action: str, params: dict, actor_role: str, actor_id: int | None, actor_name: str,
            reason: str | None, ip: str | None = None) -> ComputerCommand:
    ttl = settings.command_ttl_seconds
    cmd = ComputerCommand(computer_id=comp.id, action=action, params=params, reason=reason, issued_by_role=actor_role,
                          issued_by_id=actor_id, issued_by_name=actor_name, expires_at=utcnow() + timedelta(seconds=ttl))
    db.add(cmd)
    db.commit()
    if action == "wake":
        try:
            if not comp.mac_address:
                raise ValueError("MAC address komputer belum diketahui")
            send_wol(comp.mac_address, get_setting(db, "wol_broadcast"))
            _finish(db, cmd, "success", "Magic packet dikirim. Berhasil hanya jika BIOS/NIC mendukung Wake-on-LAN.")
        except Exception as e:
            _finish(db, cmd, "failed", str(e))
    elif action == "release":
        signed = build_signed_command(comp.device_uid, cmd.id, action, params, ttl)
        sent = hub.send_agent_threadsafe(comp.id, {"type": "command", **signed})
        comp.released = True  # token dicabut di router; agent tetap sempat menerima perintah pelepasan
        db.commit()
        _finish(db, cmd, "success" if sent else "failed", "Perangkat dilepas dari server" + ("" if sent else " (agent offline: bersihkan manual)"))
    elif not hub.is_online(comp.id):
        _finish(db, cmd, "failed", "Komputer sedang offline")
    else:
        signed = build_signed_command(comp.device_uid, cmd.id, action, params, ttl)
        # Tandai "sent" SEBELUM mengirim: balasan agent bisa tiba secepat itu dan tidak boleh tertimpa.
        cmd.status, cmd.sent_at = "sent", utcnow()
        db.commit()
        if not hub.send_agent_threadsafe(comp.id, {"type": "command", **signed}):
            _finish(db, cmd, "failed", "Gagal mengirim ke agent")
    audit.log(db, actor_role, actor_name, f"command.{action}", actor_id=actor_id, computer=comp, reason=reason, ip=ip,
              status="failed" if cmd.status == "failed" else "success", detail={"command_id": cmd.id, "params": params, "result": cmd.result})
    return cmd


def _finish(db: Session, cmd: ComputerCommand, status: str, result: str | None) -> None:
    cmd.status, cmd.result, cmd.completed_at = status, result, utcnow()
    db.commit()
    hub.emit({"type": "command_update", "data": serialize(cmd)}, classroom_id=_classroom_of(db, cmd.computer_id))


def _classroom_of(db: Session, cid: int):
    c = db.get(Computer, cid)
    return c.classroom_id if c else None


def finish_from_agent(db: Session, cmd_id: int, comp: Computer, status: str, result: str | None) -> None:
    cmd = db.get(ComputerCommand, cmd_id)
    if not cmd or cmd.computer_id != comp.id or cmd.status in ("success", "failed", "expired"):
        return
    ok = status == "success"
    _finish(db, cmd, "success" if ok else "failed", (result or "")[:2000])
    from .notify import notify
    notify(db, "command", f"Perintah {cmd.action} {'berhasil' if ok else 'gagal'}: {comp.label or comp.hostname}", result,
           severity="success" if ok else "error", computer=comp)
    audit.log(db, "agent", comp.hostname, f"command_result.{cmd.action}", computer=comp, status="success" if ok else "failed",
              detail={"command_id": cmd.id, "result": (result or "")[:500]})


def serialize(c: ComputerCommand) -> dict:
    return {"id": c.id, "computer_id": c.computer_id, "action": c.action, "params": c.params, "status": c.status,
            "result": c.result, "reason": c.reason, "issued_by": c.issued_by_name, "created_at": c.created_at.isoformat(),
            "completed_at": c.completed_at.isoformat() if c.completed_at else None}
