"""Persetujuan download & permintaan instalasi aplikasi (sisi dashboard)."""
from __future__ import annotations

from datetime import timedelta

from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy import select
from sqlalchemy.orm import Session

from ..database import get_db
from ..models import ApplicationRequest, DownloadApproval, DownloadRequest, utcnow
from ..schemas.schemas import DecisionIn
from ..services import audit
from ..services.notify import notify
from ..services.settings_service import all_settings, get_int
from ..websocket.manager import hub
from .deps import Principal, client_ip, current_user, visible_computers

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


def ser_dl(r: DownloadRequest) -> dict:
    return {"id": r.id, "computer_id": r.computer_id, "computer": r.computer.label or r.computer.hostname, "student": r.student_name,
            "file_name": r.file_name, "file_size": r.file_size, "file_type": r.file_type, "source_url": r.source_url,
            "browser": r.browser, "status": r.status, "auto_decision": r.auto_decision, "decision_note": r.decision_note,
            "decided_by": r.decided_by, "decided_at": r.decided_at.isoformat() if r.decided_at else None,
            "requested_at": r.requested_at.isoformat(), "expires_at": r.expires_at.isoformat()}


def ser_app(r: ApplicationRequest) -> dict:
    return {"id": r.id, "computer_id": r.computer_id, "computer": r.computer.label or r.computer.hostname, "student": r.student_name,
            "app_name": r.app_name, "request_type": r.request_type, "detail": r.detail, "status": r.status,
            "permit_minutes": r.permit_minutes, "decision_note": r.decision_note, "decided_by": r.decided_by,
            "decided_at": r.decided_at.isoformat() if r.decided_at else None, "requested_at": r.requested_at.isoformat()}


def expire_old(db: Session) -> int:
    n = 0
    now = utcnow()
    for r in db.execute(select(DownloadRequest).where(DownloadRequest.status == "menunggu", DownloadRequest.expires_at < now)).scalars():
        r.status, r.decided_at, r.decision_note = "kedaluwarsa", now, "Tidak dijawab tepat waktu"
        n += 1
        hub.send_agent_threadsafe(r.computer_id, {"type": "download_decision", "request_id": r.id, "status": "kedaluwarsa"})
    exp = get_int(db, "request_expiry_minutes_apps", 30)
    for r in db.execute(select(ApplicationRequest).where(ApplicationRequest.status == "menunggu",
                                                         ApplicationRequest.requested_at < now - timedelta(minutes=exp))).scalars():
        r.status, r.decided_at, r.decision_note = "kedaluwarsa", now, "Tidak dijawab tepat waktu"
        n += 1
    if n:
        db.commit()
    return n


# ---- Download ---------------------------------------------------------------------------------
@router.get("/downloads")
def list_downloads(status: str | None = Query(default=None, pattern="^(menunggu|disetujui|ditolak|kedaluwarsa)$"), limit: int = Query(200, le=1000),
                   pr: Principal = Depends(current_user), db: Session = Depends(get_db)):
    pr.require("downloads")
    expire_old(db)
    ids = [c.id for c in visible_computers(db, pr)]
    q = select(DownloadRequest).where(DownloadRequest.computer_id.in_(ids or [-1])).order_by(DownloadRequest.id.desc()).limit(limit)
    if status:
        q = q.where(DownloadRequest.status == status)
    return [ser_dl(r) for r in db.execute(q).scalars()]


def _dl(db: Session, pr: Principal, rid: int) -> DownloadRequest:
    r = db.get(DownloadRequest, rid)
    if not r or not pr.can_see(r.computer):
        raise HTTPException(404, "Permintaan tidak ditemukan")
    if r.status != "menunggu":
        raise HTTPException(409, f"Permintaan sudah berstatus {r.status}")
    if r.expires_at < utcnow():
        r.status, r.decided_at = "kedaluwarsa", utcnow()
        db.commit()
        raise HTTPException(409, "Permintaan sudah kedaluwarsa")
    return r


@router.post("/downloads/{rid}/approve")
def approve_dl(rid: int, body: DecisionIn, request: Request, pr: Principal = Depends(current_user), db: Session = Depends(get_db)):
    pr.require("downloads")
    r = _dl(db, pr, rid)
    minutes = body.minutes or get_int(db, "download_approval_minutes", 5)
    r.status, r.decided_by, r.decided_at, r.decision_note = "disetujui", pr.name, utcnow(), body.note
    ap = DownloadApproval(request_id=r.id, approved_by=pr.name, valid_until=utcnow() + timedelta(minutes=minutes), single_use=True)
    db.add(ap)
    db.commit()
    audit.log(db, pr.role, pr.name, "download.approve", actor_id=pr.id, computer=r.computer, ip=client_ip(request),
              detail={"request_id": r.id, "file": r.file_name, "url": r.source_url, "minutes": minutes}, reason=body.note)
    hub.send_agent_threadsafe(r.computer_id, {"type": "download_decision", "request_id": r.id, "status": "disetujui",
                                              "file_name": r.file_name, "source_url": r.source_url, "valid_minutes": minutes,
                                              "note": body.note})
    hub.emit({"type": "download_update", "data": ser_dl(r)}, classroom_id=r.computer.classroom_id)
    return ser_dl(r)


@router.post("/downloads/{rid}/reject")
def reject_dl(rid: int, body: DecisionIn, request: Request, pr: Principal = Depends(current_user), db: Session = Depends(get_db)):
    pr.require("downloads")
    r = _dl(db, pr, rid)
    r.status, r.decided_by, r.decided_at, r.decision_note = "ditolak", pr.name, utcnow(), body.note
    db.commit()
    audit.log(db, pr.role, pr.name, "download.reject", actor_id=pr.id, computer=r.computer, ip=client_ip(request),
              detail={"request_id": r.id, "file": r.file_name, "url": r.source_url}, reason=body.note)
    hub.send_agent_threadsafe(r.computer_id, {"type": "download_decision", "request_id": r.id, "status": "ditolak", "file_name": r.file_name,
                                              "note": body.note})
    hub.emit({"type": "download_update", "data": ser_dl(r)}, classroom_id=r.computer.classroom_id)
    return ser_dl(r)


# ---- Aplikasi ---------------------------------------------------------------------------------
@router.get("/app-requests")
def list_app_requests(status: str | None = Query(default=None, pattern="^(menunggu|disetujui|ditolak|kedaluwarsa)$"), limit: int = Query(200, le=1000),
                      pr: Principal = Depends(current_user), db: Session = Depends(get_db)):
    pr.require("apps")
    expire_old(db)
    ids = [c.id for c in visible_computers(db, pr)]
    q = select(ApplicationRequest).where(ApplicationRequest.computer_id.in_(ids or [-1])).order_by(ApplicationRequest.id.desc()).limit(limit)
    if status:
        q = q.where(ApplicationRequest.status == status)
    return [ser_app(r) for r in db.execute(q).scalars()]


def _app(db, pr, rid):
    r = db.get(ApplicationRequest, rid)
    if not r or not pr.can_see(r.computer):
        raise HTTPException(404, "Permintaan tidak ditemukan")
    if r.status != "menunggu":
        raise HTTPException(409, f"Permintaan sudah berstatus {r.status}")
    return r


@router.post("/app-requests/{rid}/approve")
def approve_app(rid: int, body: DecisionIn, request: Request, pr: Principal = Depends(current_user), db: Session = Depends(get_db)):
    pr.require("apps")
    r = _app(db, pr, rid)
    minutes = body.minutes or 15
    r.status, r.decided_by, r.decided_at, r.decision_note, r.permit_minutes = "disetujui", pr.name, utcnow(), body.note, minutes
    db.commit()
    audit.log(db, pr.role, pr.name, "app.approve", actor_id=pr.id, computer=r.computer, ip=client_ip(request),
              detail={"request_id": r.id, "app": r.app_name, "minutes": minutes}, reason=body.note)
    hub.send_agent_threadsafe(r.computer_id, {"type": "install_permit", "request_id": r.id, "status": "disetujui", "minutes": minutes,
                                              "app_name": r.app_name, "note": body.note})
    hub.emit({"type": "app_request_update", "data": ser_app(r)}, classroom_id=r.computer.classroom_id)
    return ser_app(r)


@router.post("/app-requests/{rid}/reject")
def reject_app(rid: int, body: DecisionIn, request: Request, pr: Principal = Depends(current_user), db: Session = Depends(get_db)):
    pr.require("apps")
    r = _app(db, pr, rid)
    r.status, r.decided_by, r.decided_at, r.decision_note = "ditolak", pr.name, utcnow(), body.note
    db.commit()
    audit.log(db, pr.role, pr.name, "app.reject", actor_id=pr.id, computer=r.computer, ip=client_ip(request),
              detail={"request_id": r.id, "app": r.app_name}, reason=body.note)
    hub.send_agent_threadsafe(r.computer_id, {"type": "install_permit", "request_id": r.id, "status": "ditolak", "app_name": r.app_name,
                                              "note": body.note})
    hub.emit({"type": "app_request_update", "data": ser_app(r)}, classroom_id=r.computer.classroom_id)
    return ser_app(r)


@router.get("/download-rules")
def download_rules(pr: Principal = Depends(current_user), db: Session = Depends(get_db)):
    s = all_settings(db)
    return {k: v for k, v in s.items() if k.startswith("download_")}
