"""SR / Task CRUD + status transition endpoints."""
from datetime import datetime
from typing import Any, Dict, List, Optional
from uuid import uuid4

from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession

from core.auth import get_current_user
from core.events import broadcast
from database import get_db
from models import (
    AuditLog, Institution, SRCreate, SROut, SRRequest, SRStatus,
    SRStatusUpdate, SRType, User, compute_log_hash,
    SRSubscription, SRRating, SRSignature, SRCheckin,
)

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

# Valid state transitions
_TRANSITIONS: dict[str, list[str]] = {
    SRStatus.RECEIVED:             [SRStatus.PARSED, SRStatus.REJECTED],
    SRStatus.PARSED:               [SRStatus.PENDING_APPROVAL, SRStatus.REJECTED],
    SRStatus.PENDING_APPROVAL:     [SRStatus.APPROVED, SRStatus.REJECTED],
    SRStatus.APPROVED:             [SRStatus.IN_PROGRESS, SRStatus.REJECTED],
    SRStatus.IN_PROGRESS:          [SRStatus.PENDING_PM_VALIDATION, SRStatus.FAILED_ROLLBACK],
    SRStatus.PENDING_PM_VALIDATION:[SRStatus.COMPLETED, SRStatus.FAILED_ROLLBACK],
    SRStatus.COMPLETED:            [],
    SRStatus.FAILED_ROLLBACK:      [],
    SRStatus.REJECTED:             [],
}


def _new_sr_id() -> str:
    return f"SR-{datetime.now().strftime('%Y%m%d')}-{str(uuid4())[:6].upper()}"


async def _write_audit(db: AsyncSession, sr_id: str, actor: str,
                        action: str, detail: str) -> None:
    from sqlalchemy import select as sel
    result = await db.execute(
        sel(AuditLog).where(AuditLog.sr_id == sr_id)
                      .order_by(AuditLog.id.desc()).limit(1)
    )
    last = result.scalars().first()
    prev_hash = last.log_hash if last else None
    ts = datetime.now().isoformat()
    log_hash = compute_log_hash(prev_hash, actor, action, detail, ts)
    db.add(AuditLog(
        sr_id=sr_id, actor=actor, action=action, detail=detail,
        prev_hash=prev_hash, log_hash=log_hash
    ))


async def _apply_role_filter(q, current_user: User, db: AsyncSession):
    """CUSTOMER 역할이면 자신의 기관 SR만 조회되도록 필터링."""
    from models import UserRole
    if current_user.role == UserRole.CUSTOMER and current_user.inst_code:
        inst_r = await db.execute(
            select(Institution).where(Institution.inst_code == current_user.inst_code)
        )
        inst = inst_r.scalars().first()
        if inst:
            q = q.where(SRRequest.inst_id == inst.id)
        else:
            # 기관 정보 없으면 빈 결과 보장
            q = q.where(SRRequest.id == -1)
    return q


@router.get("", response_model=List[SROut])
async def list_tasks(
    status: Optional[str]  = Query(None),
    sr_type: Optional[str] = Query(None),
    priority: Optional[str]= Query(None),
    keyword: Optional[str] = Query(None),
    skip: int = 0,
    limit: int = 50,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    q = select(SRRequest).order_by(SRRequest.created_at.desc())
    q = await _apply_role_filter(q, current_user, db)
    if status:
        q = q.where(SRRequest.status == status)
    if sr_type:
        q = q.where(SRRequest.sr_type == sr_type)
    if priority:
        q = q.where(SRRequest.priority == priority)
    if keyword:
        q = q.where(SRRequest.title.contains(keyword))
    q = q.offset(skip).limit(limit)
    result = await db.execute(q)
    return result.scalars().all()


@router.get("/stats")
async def get_stats(db: AsyncSession = Depends(get_db),
                    current_user: User = Depends(get_current_user)):
    # 기관 필터 (CUSTOMER)
    base_q = select(func.count(SRRequest.id))
    filtered = await _apply_role_filter(select(SRRequest), current_user, db)
    # subquery approach: get allowed sr ids
    from sqlalchemy import and_
    inst_filter = None
    from models import UserRole
    if current_user.role == UserRole.CUSTOMER and current_user.inst_code:
        inst_r = await db.execute(
            select(Institution).where(Institution.inst_code == current_user.inst_code)
        )
        inst = inst_r.scalars().first()
        inst_filter = (SRRequest.inst_id == inst.id) if inst else (SRRequest.id == -1)

    def _cnt(extra=None):
        q = base_q
        if inst_filter is not None:
            q = q.where(inst_filter)
        if extra is not None:
            q = q.where(extra)
        return q

    total = (await db.execute(_cnt())).scalar() or 0
    by_status: dict[str, int] = {}
    for s in SRStatus:
        cnt = (await db.execute(_cnt(SRRequest.status == s))).scalar() or 0
        if cnt:
            by_status[s.value] = cnt
    by_type: dict[str, int] = {}
    for t in SRType:
        cnt = (await db.execute(_cnt(SRRequest.sr_type == t))).scalar() or 0
        if cnt:
            by_type[t.value] = cnt
    return {"total": total, "by_status": by_status, "by_type": by_type}


@router.get("/{sr_id}", response_model=SROut)
async def get_task(sr_id: str, db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(SRRequest).where(SRRequest.sr_id == sr_id))
    sr = result.scalars().first()
    if not sr:
        raise HTTPException(404, detail="SR을 찾을 수 없습니다.")
    return sr


@router.post("", response_model=SROut, status_code=201)
async def create_task(payload: SRCreate, db: AsyncSession = Depends(get_db)):
    inst_id = None
    if payload.inst_code:
        r = await db.execute(
            select(Institution).where(Institution.inst_code == payload.inst_code)
        )
        inst = r.scalars().first()
        if inst:
            inst_id = inst.id

    sr = SRRequest(
        sr_id=_new_sr_id(),
        inst_id=inst_id,
        sr_type=payload.sr_type,
        title=payload.title,
        description=payload.description,
        status=SRStatus.RECEIVED,
        priority=payload.priority,
        requested_by=payload.requested_by,
        assigned_to=payload.assigned_to,
        target_server=payload.target_server,
    )
    db.add(sr)
    await db.flush()
    await _write_audit(db, sr.sr_id, payload.requested_by, "SR_CREATED", f"SR 생성: {payload.title}")

    # 담당자가 미지정이면 자동 배정 시도
    if not sr.assigned_to:
        from routers.assign import auto_assign_engine
        assigned = await auto_assign_engine(db, sr)
        if assigned:
            sr.assigned_to = assigned
            await _write_audit(db, sr.sr_id, "SYSTEM", "ENGINEER_ASSIGNED",
                               f"자동 배정: {assigned}")

    # A-2: SLA 마감 시각 계산·저장
    from core.sla import set_sla_on_create
    await set_sla_on_create(sr.sr_id, db)
    await db.refresh(sr)

    await db.commit()
    await db.refresh(sr)
    # 실시간 이벤트 브로드캐스트
    await broadcast("sr_created", {
        "sr_id":        sr.sr_id,
        "title":        sr.title,
        "sr_type":      sr.sr_type,
        "priority":     sr.priority,
        "status":       sr.status,
        "sla_deadline": sr.sla_deadline.isoformat() if sr.sla_deadline else None,
    })
    # 알림 발송 (이메일 + 메신저) — 비동기 fire-and-forget
    import asyncio as _asyncio
    from core.notify import notify_sr_created as _notify_created
    _asyncio.create_task(_notify_created(sr))

    # 학습 루프: 재발 패턴 감지 — fire-and-forget
    async def _detect_recurrence_bg():
        from database import SessionLocal
        from core.learning import detect_recurrence
        try:
            async with SessionLocal() as _db:
                result = await detect_recurrence(
                    db          = _db,
                    sr_id       = sr.sr_id,
                    title       = sr.title,
                    description = sr.description or "",
                    sr_type     = sr.sr_type or "OTHER",
                    inst_id     = sr.inst_id,
                )
                if result.get("recurrence_found"):
                    import logging
                    logging.getLogger(__name__).info(
                        "재발 감지: SR=%s 패턴=%d 횟수=%d%s",
                        sr.sr_id,
                        result["pattern_id"],
                        result["occurrence_count"],
                        " → Problem 격상" if result.get("escalated") else "",
                    )
        except Exception as _e:
            import logging
            logging.getLogger(__name__).debug("재발 감지 오류 (무시): %s", _e)

    _asyncio.create_task(_detect_recurrence_bg())

    # G-7: AI 자동 분류 — fire-and-forget
    async def _apply_ai_classification_bg():
        from database import SessionLocal
        from core.ticket_classifier import classify_ticket
        import json as _json
        try:
            suggestion = await classify_ticket(sr.title, sr.description or "")
            async with SessionLocal() as _db:
                _sr = (await _db.execute(
                    select(SRRequest).where(SRRequest.sr_id == sr.sr_id)
                )).scalars().first()
                if _sr:
                    _sr.ai_suggestion = _json.dumps(suggestion, ensure_ascii=False)
                    await _db.commit()
        except Exception:
            pass

    _asyncio.create_task(_apply_ai_classification_bg())

    # SR 자동 리뷰 — fire-and-forget
    # 분류 검증 → 관련 서버 tmux 스냅샷 → Ollama AI 리뷰 → TB_SR_AUTO_REVIEW 저장
    async def _sr_auto_review_bg():
        from routers.sr_auto_review import run_sr_review
        await run_sr_review(sr.sr_id)

    _asyncio.create_task(_sr_auto_review_bg())

    return sr


@router.patch("/{sr_id}/status", response_model=SROut)
async def update_status(sr_id: str, payload: SRStatusUpdate,
                         db: AsyncSession = Depends(get_db),
                         _u: User = Depends(get_current_user)):
    result = await db.execute(select(SRRequest).where(SRRequest.sr_id == sr_id))
    sr = result.scalars().first()
    if not sr:
        raise HTTPException(404, detail="SR을 찾을 수 없습니다.")

    allowed = _TRANSITIONS.get(SRStatus(sr.status), [])
    if payload.status not in allowed:
        raise HTTPException(400, detail=f"'{sr.status}' → '{payload.status}' 전이는 허용되지 않습니다.")

    old_status = sr.status
    sr.status = payload.status
    sr.updated_at = datetime.now()
    detail = payload.comment or f"상태 변경: {old_status} → {payload.status}"
    await _write_audit(db, sr_id, payload.actor, "STATUS_CHANGED", detail)
    await db.commit()
    await db.refresh(sr)
    # 실시간 이벤트 브로드캐스트
    await broadcast("sr_updated", {
        "sr_id":      sr_id,
        "title":      sr.title,
        "old_status": old_status,
        "new_status": sr.status,
        "actor":      payload.actor,
    })
    # 알림 발송 (COMPLETED / REJECTED / FAILED_ROLLBACK 시)
    import asyncio as _asyncio
    from core.notify import notify_sr_status_changed as _notify_changed
    _asyncio.create_task(_notify_changed(sr, sr.status, payload.comment or ""))
    return sr


# ── A-2: SLA 조회 엔드포인트 ──────────────────────────────────────────────────

@router.get("/{sr_id}/sla")
async def get_sla_status(
    sr_id: str,
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """
    특정 SR의 SLA 현황 조회.

    Returns:
        {
          "sr_id": "SR-...",
          "priority": "HIGH",
          "sla_deadline": "2026-05-26T14:00:00",
          "sla_breached": false,
          "remaining_minutes": 47,
          "escalated_at": null,
          "escalated_to": null
        }
    """
    from core.sla import sla_remaining_minutes
    result = await db.execute(select(SRRequest).where(SRRequest.sr_id == sr_id))
    sr = result.scalars().first()
    if not sr:
        raise HTTPException(404, "SR을 찾을 수 없습니다.")

    remaining = sla_remaining_minutes(sr.sla_deadline)
    return {
        "sr_id":             sr.sr_id,
        "priority":          sr.priority,
        "sla_deadline":      sr.sla_deadline.isoformat() if sr.sla_deadline else None,
        "sla_breached":      sr.sla_breached,
        "remaining_minutes": remaining,
        "escalated_at":      sr.escalated_at.isoformat() if sr.escalated_at else None,
        "escalated_to":      sr.escalated_to,
    }


@router.get("/sla/violations")
async def list_sla_violations(
    skip: int = 0,
    limit: int = 50,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    SLA 위반 중인 SR 목록.
    ADMIN / PM / ENGINEER 만 접근 가능.
    """
    from models import UserRole
    from sqlalchemy import and_
    from core.sla import sla_remaining_minutes

    if current_user.role == UserRole.CUSTOMER:
        raise HTTPException(403, "권한이 없습니다.")

    terminal = ["COMPLETED", "REJECTED", "FAILED_ROLLBACK"]
    now = datetime.now()
    q = (
        select(SRRequest)
        .where(
            and_(
                SRRequest.sla_deadline.isnot(None),
                SRRequest.sla_deadline < now,
                SRRequest.status.notin_(terminal),
            )
        )
        .order_by(SRRequest.sla_deadline.asc())
        .offset(skip).limit(limit)
    )
    rows = (await db.execute(q)).scalars().all()

    return [
        {
            "sr_id":             r.sr_id,
            "title":             r.title,
            "priority":          r.priority,
            "status":            r.status,
            "assigned_to":       r.assigned_to,
            "sla_deadline":      r.sla_deadline.isoformat() if r.sla_deadline else None,
            "overdue_minutes":   abs(sla_remaining_minutes(r.sla_deadline) or 0),
            "sla_breached":      r.sla_breached,
            "escalated_to":      r.escalated_to,
        }
        for r in rows
    ]


# ── G-7: AI 분류 결과 조회 ────────────────────────────────────────────────────

@router.get("/{sr_id}/ai-suggestion")
async def get_ai_suggestion(
    sr_id: str,
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """SR의 AI 자동 분류 제안 조회."""
    import json as _json
    r = await db.execute(select(SRRequest).where(SRRequest.sr_id == sr_id))
    sr = r.scalars().first()
    if not sr:
        raise HTTPException(404, "SR을 찾을 수 없습니다.")

    if not sr.ai_suggestion:
        # 즉시 분류 요청
        from core.ticket_classifier import classify_ticket
        try:
            suggestion = await classify_ticket(sr.title, sr.description or "")
            sr.ai_suggestion = _json.dumps(suggestion, ensure_ascii=False)
            await db.commit()
        except Exception as e:
            return {"sr_id": sr_id, "suggestion": None, "message": f"AI 분류 실패: {str(e)[:100]}"}

    try:
        return {"sr_id": sr_id, "suggestion": _json.loads(sr.ai_suggestion)}
    except Exception:
        return {"sr_id": sr_id, "suggestion": None}


# ── G-2: SR 대량 처리 ─────────────────────────────────────────────────────────

class BulkActionRequest(BaseModel):
    sr_ids: List[str]
    action: str  # STATUS_CHANGE | ASSIGN | CLOSE | PRIORITY_CHANGE
    params: Dict[str, Any] = {}


@router.post("/bulk")
async def bulk_sr_action(
    payload: BulkActionRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    SR 대량 작업 (최대 100건).
    action: STATUS_CHANGE | ASSIGN | CLOSE | PRIORITY_CHANGE
    """
    from models import UserRole
    if current_user.role == UserRole.CUSTOMER:
        raise HTTPException(403, "대량 작업은 ADMIN/PM/ENGINEER만 가능합니다.")
    if not payload.sr_ids:
        raise HTTPException(400, "sr_ids가 비어 있습니다. 처리할 SR ID를 입력하세요.")
    if len(payload.sr_ids) > 100:
        raise HTTPException(400, "한 번에 최대 100건까지 처리 가능합니다.")

    results = []
    success = 0
    for sr_id in payload.sr_ids:
        try:
            r = await db.execute(select(SRRequest).where(SRRequest.sr_id == sr_id))
            sr = r.scalars().first()
            if not sr:
                results.append({"sr_id": sr_id, "ok": False, "error": "SR을 찾을 수 없습니다."})
                continue

            action = payload.action.upper()
            if action == "STATUS_CHANGE":
                new_status = payload.params.get("status")
                if not new_status:
                    raise ValueError("status 파라미터 필요")
                allowed = _TRANSITIONS.get(SRStatus(sr.status), [])
                if new_status not in allowed:
                    raise ValueError(f"'{sr.status}' → '{new_status}' 전이 불가")
                sr.status = new_status
                sr.updated_at = datetime.now()
                note = payload.params.get("note", f"대량 상태변경: {new_status}")
                await _write_audit(db, sr_id, current_user.username, "BULK_STATUS_CHANGE", note)

            elif action == "ASSIGN":
                assignee = payload.params.get("assignee")
                if not assignee:
                    raise ValueError("assignee 파라미터 필요")
                sr.assigned_to = assignee
                sr.updated_at = datetime.now()
                await _write_audit(db, sr_id, current_user.username, "BULK_ASSIGN",
                                   f"대량 배정: {assignee}")

            elif action == "CLOSE":
                if sr.status in (SRStatus.COMPLETED, SRStatus.REJECTED, SRStatus.FAILED_ROLLBACK):
                    raise ValueError(f"이미 종료된 SR: {sr.status}")
                sr.status = SRStatus.COMPLETED
                sr.updated_at = datetime.now()
                note = payload.params.get("note", "대량 완료 처리")
                await _write_audit(db, sr_id, current_user.username, "BULK_CLOSE", note)

            elif action == "PRIORITY_CHANGE":
                new_prio = payload.params.get("priority")
                if not new_prio:
                    raise ValueError("priority 파라미터 필요")
                sr.priority = new_prio
                sr.updated_at = datetime.now()
                await _write_audit(db, sr_id, current_user.username, "BULK_PRIORITY_CHANGE",
                                   f"우선순위 변경: {new_prio}")
            else:
                raise ValueError(f"알 수 없는 action: {action}")

            await db.flush()
            results.append({"sr_id": sr_id, "ok": True, "error": None})
            success += 1

        except Exception as e:
            results.append({"sr_id": sr_id, "ok": False, "error": str(e)})

    await db.commit()
    return {
        "total":   len(payload.sr_ids),
        "success": success,
        "failed":  len(payload.sr_ids) - success,
        "results": results,
    }


# ════════════════════════════════════════════════════════════════════════════════
# ── 모바일 100기능: SR 액션 + 통계 ──────────────────────────────────────────────
# ════════════════════════════════════════════════════════════════════════════════

class EscalateRequest(BaseModel):
    reason: Optional[str] = None
    escalate_to: Optional[str] = None   # 미지정 시 온콜 에스컬레이션 체인 사용


class RatingRequest(BaseModel):
    score: int
    comment: Optional[str] = None


class SignatureRequest(BaseModel):
    signature_base64: str


class SlaExceptionRequest(BaseModel):
    reason: str
    new_deadline: str   # ISO datetime/date


class CheckinRequest(BaseModel):
    lat: float
    lng: float


async def _get_sr(sr_id: str, db: AsyncSession) -> SRRequest:
    sr = (await db.execute(
        select(SRRequest).where(SRRequest.sr_id == sr_id)
    )).scalars().first()
    if not sr:
        raise HTTPException(404, detail="SR을 찾을 수 없습니다.")
    return sr


@router.post("/{sr_id}/escalate")
async def escalate_task(
    sr_id: str,
    payload: EscalateRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """SR 에스컬레이션 — 상위 담당자로 재배정 + 알림 (#8)."""
    from models import UserRole
    if current_user.role == UserRole.CUSTOMER:
        raise HTTPException(403, "에스컬레이션 권한이 없습니다.")

    sr = await _get_sr(sr_id, db)

    target = payload.escalate_to
    if not target:
        # 온콜 에스컬레이션 체인에서 대상 도출 시도
        try:
            from core.oncall_rotate import get_current_oncall
            sched = await get_current_oncall(db)
            if sched:
                target = sched.escalation_to or sched.backup_engineer or sched.engineer
        except Exception:
            target = None

    sr.escalated_at = datetime.now()
    sr.escalated_to = target
    if target:
        sr.assigned_to = target
    sr.updated_at = datetime.now()
    await _write_audit(db, sr_id, current_user.username, "SR_ESCALATED",
                       f"에스컬레이션 → {target or '미정'} | 사유: {payload.reason or ''}")
    await db.commit()
    await broadcast("sla_escalated", {
        "sr_id": sr_id, "escalated_to": target, "by": current_user.username,
    })
    return {
        "sr_id": sr_id,
        "escalated_to": target,
        "escalated_at": sr.escalated_at.isoformat(),
        "message": f"'{target or '대상 미정'}'(으)로 에스컬레이션되었습니다.",
    }


@router.post("/{sr_id}/subscribe")
async def toggle_subscribe(
    sr_id: str,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """SR 구독/팔로우 토글 (#14)."""
    await _get_sr(sr_id, db)
    existing = (await db.execute(
        select(SRSubscription).where(
            SRSubscription.task_id == sr_id,
            SRSubscription.username == current_user.username,
        )
    )).scalars().first()

    if existing:
        await db.delete(existing)
        await db.commit()
        return {"sr_id": sr_id, "subscribed": False, "message": "구독을 해제했습니다."}

    sub = SRSubscription(task_id=sr_id, username=current_user.username)
    db.add(sub)
    await db.commit()
    return {"sr_id": sr_id, "subscribed": True, "message": "구독했습니다."}


@router.post("/{sr_id}/rating", status_code=201)
async def rate_task(
    sr_id: str,
    payload: RatingRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """완료 SR 만족도 평가 (#53)."""
    if not (1 <= payload.score <= 5):
        raise HTTPException(422, "score는 1~5 범위여야 합니다.")
    sr = await _get_sr(sr_id, db)
    if sr.status != SRStatus.COMPLETED:
        raise HTTPException(400, "완료된 SR만 평가할 수 있습니다.")

    rating = SRRating(
        task_id=sr_id, rater=current_user.username,
        score=payload.score, comment=payload.comment,
    )
    db.add(rating)
    await _write_audit(db, sr_id, current_user.username, "SR_RATED",
                       f"만족도 {payload.score}점")
    await db.commit()
    await db.refresh(rating)
    return {"sr_id": sr_id, "score": payload.score, "rating_id": rating.id}


@router.post("/{sr_id}/signature", status_code=201)
async def save_signature(
    sr_id: str,
    payload: SignatureRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """현장 전자서명 저장 (#70)."""
    if not payload.signature_base64 or len(payload.signature_base64) < 10:
        raise HTTPException(422, "signature_base64 데이터가 올바르지 않습니다.")
    await _get_sr(sr_id, db)

    sig = SRSignature(
        task_id=sr_id, signed_by=current_user.username,
        signature_b64=payload.signature_base64,
    )
    db.add(sig)
    await _write_audit(db, sr_id, current_user.username, "SR_SIGNED", "현장 서명 등록")
    await db.commit()
    await db.refresh(sig)
    return {"sr_id": sr_id, "signature_id": sig.id, "signed_by": current_user.username}


@router.post("/{sr_id}/sla-exception")
async def request_sla_exception(
    sr_id: str,
    payload: SlaExceptionRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """SLA 예외 승인 요청 — SLA 마감 연장 요청 (#94)."""
    sr = await _get_sr(sr_id, db)
    try:
        new_dl = datetime.fromisoformat(payload.new_deadline)
    except ValueError:
        raise HTTPException(422, "new_deadline은 ISO 날짜/시간 형식이어야 합니다.")

    old_dl = sr.sla_deadline
    sr.sla_deadline = new_dl
    sr.sla_breached = False
    sr.updated_at = datetime.now()
    await _write_audit(db, sr_id, current_user.username, "SLA_EXCEPTION_REQUESTED",
                       f"SLA 마감 {old_dl} → {new_dl} | 사유: {payload.reason}")
    await db.commit()
    return {
        "sr_id": sr_id,
        "old_deadline": old_dl.isoformat() if old_dl else None,
        "new_deadline": new_dl.isoformat(),
        "reason": payload.reason,
        "message": "SLA 예외(마감 연장)가 적용되었습니다.",
    }


@router.post("/{sr_id}/checkin", status_code=201)
async def checkin_task(
    sr_id: str,
    payload: CheckinRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """현장 체크인 — GPS 좌표 기록 (#93)."""
    if not (-90 <= payload.lat <= 90) or not (-180 <= payload.lng <= 180):
        raise HTTPException(422, "좌표 값이 올바르지 않습니다.")
    await _get_sr(sr_id, db)

    chk = SRCheckin(
        task_id=sr_id, username=current_user.username,
        lat=payload.lat, lng=payload.lng,
    )
    db.add(chk)
    await _write_audit(db, sr_id, current_user.username, "SR_CHECKIN",
                       f"현장 체크인 ({payload.lat}, {payload.lng})")
    await db.commit()
    await db.refresh(chk)
    return {
        "sr_id": sr_id,
        "checkin_id": chk.id,
        "lat": payload.lat,
        "lng": payload.lng,
        "checked_in_at": chk.created_at.isoformat() if chk.created_at else None,
    }


@router.get("/stats/mine")
async def my_sr_stats(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """내 SR 처리 통계 — total / closed / avg_resolve_hours / sla_met_rate (#3)."""
    rows = (await db.execute(
        select(SRRequest).where(SRRequest.assigned_to == current_user.username)
    )).scalars().all()

    total = len(rows)
    terminal = {SRStatus.COMPLETED, SRStatus.REJECTED, SRStatus.FAILED_ROLLBACK}
    closed_rows = [r for r in rows if r.status in terminal]
    closed = len(closed_rows)

    # 평균 해결 시간 (완료 건의 created_at→updated_at)
    completed = [r for r in rows if r.status == SRStatus.COMPLETED
                 and r.created_at and r.updated_at]
    avg_hours = 0.0
    if completed:
        secs = sum((r.updated_at - r.created_at).total_seconds() for r in completed)
        avg_hours = round(secs / len(completed) / 3600, 2)

    # SLA 준수율 (마감 시각이 있는 건 중 미위반 비율)
    sla_rows = [r for r in rows if r.sla_deadline is not None]
    sla_met = len([r for r in sla_rows if not r.sla_breached])
    sla_met_rate = round(sla_met / len(sla_rows) * 100, 1) if sla_rows else 100.0

    return {
        "username": current_user.username,
        "total": total,
        "closed": closed,
        "open": total - closed,
        "avg_resolve_hours": avg_hours,
        "sla_met_rate": sla_met_rate,
    }


@router.get("/stats/by-institution")
async def stats_by_institution(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """기관별 SR 현황 비교 (#4). CUSTOMER는 자기 기관만."""
    from models import UserRole

    q = select(SRRequest)
    cust_inst_id = None
    if current_user.role == UserRole.CUSTOMER and current_user.inst_code:
        inst = (await db.execute(
            select(Institution).where(Institution.inst_code == current_user.inst_code)
        )).scalars().first()
        cust_inst_id = inst.id if inst else -1
        q = q.where(SRRequest.inst_id == cust_inst_id)

    srs = (await db.execute(q)).scalars().all()

    # 기관 이름 매핑
    insts = (await db.execute(select(Institution))).scalars().all()
    name_map = {i.id: i.inst_name for i in insts}

    terminal = {SRStatus.COMPLETED, SRStatus.REJECTED, SRStatus.FAILED_ROLLBACK}
    agg: dict = {}
    for s in srs:
        key = s.inst_id
        bucket = agg.setdefault(key, {
            "inst_id": key,
            "inst_name": name_map.get(key, "미지정"),
            "total": 0, "closed": 0, "open": 0, "sla_breached": 0,
        })
        bucket["total"] += 1
        if s.status in terminal:
            bucket["closed"] += 1
        else:
            bucket["open"] += 1
        if s.sla_breached:
            bucket["sla_breached"] += 1

    result = sorted(agg.values(), key=lambda x: x["total"], reverse=True)
    return {"institutions": result, "count": len(result)}

