"""
Engineer assignment router — skill matching + workload balancing.

알고리즘:
  score = +3 (SR 유형 스킬 일치)
        + +2 (담당 기관 친화도 일치)
        + -1 × 현재 활성 SR 수 (워크로드 페널티)
  가장 높은 점수의 엔지니어를 배정. max_workload 초과 시 제외.
"""
from datetime import datetime
from typing import Optional

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

from core.auth import get_current_user
from database import get_db
from models import (
    AuditLog, EngineerProfile, Institution, SRRequest, SRStatus,
    User, UserRole, compute_log_hash,
)

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

# 워크로드 계산에 포함할 활성 상태
_ACTIVE = [
    SRStatus.RECEIVED.value,
    SRStatus.PARSED.value,
    SRStatus.PENDING_APPROVAL.value,
    SRStatus.APPROVED.value,
    SRStatus.IN_PROGRESS.value,
    SRStatus.PENDING_PM_VALIDATION.value,
]


# ── Internal helpers ───────────────────────────────────────────────────────────

async def _write_audit(db: AsyncSession, sr_id: str,
                        actor: str, action: str, detail: str) -> None:
    result = await db.execute(
        select(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 auto_assign_engine(db: AsyncSession, sr: SRRequest) -> Optional[str]:
    """
    스킬 매칭 + 워크로드 밸런싱 자동 배정 엔진.
    배정된 엔지니어 username 반환. 적합한 엔지니어 없으면 None.
    """
    res = await db.execute(
        select(EngineerProfile).where(EngineerProfile.is_available == True)
    )
    profiles = res.scalars().all()
    if not profiles:
        return None

    # 기관 코드 조회 (친화도 매칭용)
    inst_code = None
    if sr.inst_id:
        r = await db.execute(select(Institution).where(Institution.id == sr.inst_id))
        inst = r.scalars().first()
        if inst:
            inst_code = inst.inst_code

    scored = []
    for p in profiles:
        wl = await db.execute(
            select(func.count(SRRequest.id)).where(
                SRRequest.assigned_to == p.username,
                SRRequest.status.in_(_ACTIVE),
            )
        )
        workload = wl.scalar() or 0
        if workload >= p.max_workload:
            continue  # 포화 상태

        score = 0
        skills = [s.strip() for s in (p.skill_types or "").split(",") if s.strip()]
        affinities = [a.strip() for a in (p.inst_affinity or "").split(",") if a.strip()]

        if sr.sr_type in skills:
            score += 3
        if inst_code and inst_code in affinities:
            score += 2
        score -= workload  # 워크로드 페널티

        scored.append((score, workload, p.username))

    if not scored:
        return None

    # 점수 내림차순, 워크로드 오름차순 정렬
    scored.sort(key=lambda x: (-x[0], x[1]))
    return scored[0][2]


# ── Endpoints ──────────────────────────────────────────────────────────────────

@router.get("/workload")
async def get_workload(
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """엔지니어별 현재 워크로드 현황."""
    res = await db.execute(select(EngineerProfile))
    profiles = res.scalars().all()

    result = []
    for p in profiles:
        active_cnt = (await db.execute(
            select(func.count(SRRequest.id)).where(
                SRRequest.assigned_to == p.username,
                SRRequest.status.in_(_ACTIVE),
            )
        )).scalar() or 0

        done_cnt = (await db.execute(
            select(func.count(SRRequest.id)).where(
                SRRequest.assigned_to == p.username,
                SRRequest.status == SRStatus.COMPLETED.value,
            )
        )).scalar() or 0

        result.append({
            "username":      p.username,
            "display_name":  p.display_name or p.username,
            "skill_types":   p.skill_types or "",
            "inst_affinity": p.inst_affinity or "",
            "active":        active_cnt,
            "max_workload":  p.max_workload,
            "completed":     done_cnt,
            "is_available":  p.is_available,
            "utilization":   round(active_cnt / p.max_workload * 100) if p.max_workload else 0,
        })

    return result


@router.get("/engineers")
async def list_engineers(
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """수동 배정용 엔지니어 목록."""
    res = await db.execute(
        select(EngineerProfile).where(EngineerProfile.is_available == True)
    )
    return [
        {"username": p.username, "display_name": p.display_name or p.username}
        for p in res.scalars().all()
    ]


@router.post("/{sr_id}")
async def assign_engineer(
    sr_id: str,
    engineer: Optional[str] = None,   # None → 자동 배정
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    SR에 엔지니어 배정 또는 재배정.
    - engineer 파라미터 없음 → 스킬 매칭 자동 배정
    - engineer 지정 → 수동 배정 (ADMIN/PM만 가능)
    """
    if current_user.role not in (UserRole.ADMIN, UserRole.PM, UserRole.ENGINEER):
        raise HTTPException(403, "배정 권한이 없습니다.")

    r = await db.execute(select(SRRequest).where(SRRequest.sr_id == sr_id))
    sr = r.scalars().first()
    if not sr:
        raise HTTPException(404, "SR을 찾을 수 없습니다.")

    if engineer:
        # 수동 배정 — ADMIN/PM만 허용
        if current_user.role == UserRole.ENGINEER:
            raise HTTPException(403, "수동 배정은 PM 또는 관리자만 가능합니다.")
        ue = await db.execute(select(User).where(User.username == engineer))
        u  = ue.scalars().first()
        if not u or u.role != UserRole.ENGINEER:
            raise HTTPException(400, f"'{engineer}'는 엔지니어 사용자가 아닙니다.")
        assigned = engineer
    else:
        assigned = await auto_assign_engine(db, sr)
        if not assigned:
            raise HTTPException(503, "현재 배정 가능한 엔지니어가 없습니다.")

    prev  = sr.assigned_to or "미배정"
    actor = current_user.display_name or current_user.username
    sr.assigned_to = assigned
    sr.updated_at  = datetime.now()
    detail = f"엔지니어 배정: {prev} → {assigned}"
    await _write_audit(db, sr_id, actor, "ENGINEER_ASSIGNED", detail)
    await db.commit()
    await db.refresh(sr)

    return {
        "sr_id":       sr_id,
        "assigned_to": assigned,
        "prev":        prev,
        "detail":      detail,
    }
