"""PM Approval workflow endpoints."""
import hashlib
from datetime import datetime
from typing import List, Optional

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy import select
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 (
    ApprovalCreate, ApprovalFlow, ApprovalOut, ApprovalResult,
    SRRequest, SRStatus, AuditLog, User, compute_log_hash
)

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


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
    ))


class DelegateByUsernameRequest(BaseModel):
    delegate_to: str          # 대리 결재자 username
    from_date: str            # ISO date
    to_date: str              # ISO date
    reason: Optional[str] = None


@router.post("/delegate")
async def delegate_global(
    body: DelegateByUsernameRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """기간 기반 대리 결재 위임 — 현재 사용자의 대기 결재를 일괄 위임 (#65).

    주의: 이 라우트는 POST /{sr_id} 보다 먼저 정의되어야 'delegate'가
    sr_id 경로 변수로 잘못 매칭되지 않는다.
    """
    try:
        from_dt = datetime.fromisoformat(body.from_date)
        to_dt = datetime.fromisoformat(body.to_date)
    except ValueError:
        raise HTTPException(422, "from_date / to_date는 ISO 날짜 형식이어야 합니다.")
    if to_dt < from_dt:
        raise HTTPException(422, "to_date는 from_date 이후여야 합니다.")

    delegate_user = (await db.execute(
        select(User).where(User.username == body.delegate_to)
    )).scalars().first()
    if not delegate_user:
        raise HTTPException(404, f"대리 결재자 '{body.delegate_to}'를 찾을 수 없습니다.")

    pending = (await db.execute(
        select(ApprovalFlow).where(
            ApprovalFlow.approver == current_user.username,
            ApprovalFlow.result == ApprovalResult.PENDING,
        )
    )).scalars().all()

    delegated = 0
    for apv in pending:
        apv.delegate_to = delegate_user.id
        apv.delegate_until = to_dt
        delegated += 1
    if pending:
        await _write_audit(
            db, pending[0].sr_id, current_user.username, "APPROVAL_DELEGATED_BULK",
            f"대리 결재 → {delegate_user.username} ({body.from_date}~{body.to_date}) "
            f"| {delegated}건 | 사유: {body.reason or ''}"
        )
    await db.commit()
    return {
        "delegate_to": delegate_user.username,
        "from_date": body.from_date,
        "to_date": body.to_date,
        "delegated_count": delegated,
        "message": f"{delegated}건의 결재가 위임되었습니다.",
    }


@router.get("/{sr_id}", response_model=List[ApprovalOut])
async def list_approvals(sr_id: str, db: AsyncSession = Depends(get_db),
                         _u: User = Depends(get_current_user)):
    result = await db.execute(
        select(ApprovalFlow).where(ApprovalFlow.sr_id == sr_id)
                             .order_by(ApprovalFlow.created_at)
    )
    return result.scalars().all()


@router.post("/{sr_id}", response_model=ApprovalOut, status_code=201)
async def decide_approval(sr_id: str, payload: ApprovalCreate,
                            db: AsyncSession = Depends(get_db),
                            _u: User = Depends(get_current_user)):
    r = await db.execute(select(SRRequest).where(SRRequest.sr_id == sr_id))
    sr = r.scalars().first()
    if not sr:
        raise HTTPException(404, detail="SR을 찾을 수 없습니다.")
    if sr.status != SRStatus.PENDING_APPROVAL:
        raise HTTPException(400, detail="승인 대기 상태의 SR만 처리할 수 있습니다.")

    apv = ApprovalFlow(
        sr_id=sr_id,
        approver=payload.approver,
        result=payload.result,
        comment=payload.comment,
        decided_at=datetime.now(),
    )
    db.add(apv)

    if payload.result == ApprovalResult.APPROVED:
        sr.status = SRStatus.APPROVED
        action, detail = "SR_APPROVED", f"{payload.approver} 승인"
    else:
        sr.status = SRStatus.REJECTED
        action, detail = "SR_REJECTED", f"{payload.approver} 반려: {payload.comment or ''}"

    sr.updated_at = datetime.now()
    await _write_audit(db, sr_id, payload.approver, action, detail)
    await db.commit()
    await db.refresh(apv)
    # 실시간 이벤트 브로드캐스트
    await broadcast("approval_done", {
        "sr_id":      sr_id,
        "title":      sr.title,
        "result":     payload.result,
        "approver":   payload.approver,
        "new_status": sr.status,
    })
    return apv


# ── G-11: 다중 승인 체계 ─────────────────────────────────────────────────────

class DelegateRequest(BaseModel):
    delegate_to: int   # User.id
    until:       str   # ISO datetime
    reason:      Optional[str] = None


class SignatureRequest(BaseModel):
    signature: str  # 전자서명 해시 (비밀번호 해시 or 서명 데이터)


class ExtendDeadlineRequest(BaseModel):
    new_deadline: str  # ISO datetime
    reason:       Optional[str] = None


@router.post("/{approval_id}/delegate")
async def delegate_approval(
    approval_id: int,
    body: DelegateRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """결재 위임 — 현재 결재자만 가능."""
    apv = await db.get(ApprovalFlow, approval_id)
    if not apv:
        raise HTTPException(404, "승인 레코드를 찾을 수 없습니다.")
    if apv.approver != current_user.username:
        raise HTTPException(403, "본인의 결재만 위임할 수 있습니다.")
    if apv.result != ApprovalResult.PENDING:
        raise HTTPException(400, "이미 처리된 결재는 위임할 수 없습니다.")

    try:
        delegate_until = datetime.fromisoformat(body.until)
    except ValueError:
        raise HTTPException(400, "until 필드는 ISO 날짜시간 형식이어야 합니다.")

    # 위임 대상 사용자 확인
    delegate_user = await db.get(User, body.delegate_to)
    if not delegate_user:
        raise HTTPException(404, f"위임 대상 사용자 ID {body.delegate_to}를 찾을 수 없습니다.")

    apv.delegate_to    = body.delegate_to
    apv.delegate_until = delegate_until
    await _write_audit(
        db, apv.sr_id, current_user.username, "APPROVAL_DELEGATED",
        f"결재 위임 → {delegate_user.username} (until {body.until}) | 사유: {body.reason or ''}"
    )
    await db.commit()
    return {
        "approval_id":    approval_id,
        "delegated_to":   delegate_user.username,
        "delegate_until": body.until,
        "message":        f"{delegate_user.username}에게 결재가 위임되었습니다.",
    }


@router.post("/{approval_id}/sign")
async def sign_approval(
    approval_id: int,
    body: SignatureRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """전자서명 등록 — 승인 결재자 또는 위임받은 사용자만 가능."""
    apv = await db.get(ApprovalFlow, approval_id)
    if not apv:
        raise HTTPException(404, "승인 레코드를 찾을 수 없습니다.")
    if apv.result != ApprovalResult.PENDING:
        raise HTTPException(400, "대기 중인 결재에만 서명할 수 있습니다.")

    # 서명자 검증
    is_delegate = (
        apv.delegate_to == current_user.id
        and apv.delegate_until
        and apv.delegate_until >= datetime.now()
    )
    if apv.approver != current_user.username and not is_delegate:
        raise HTTPException(403, "결재 권한이 없습니다.")

    # 서명 해시 저장 (평문 서명 데이터는 저장 금지)
    sig_hash = hashlib.sha256(body.signature.encode()).hexdigest()
    apv.signature = sig_hash
    await _write_audit(
        db, apv.sr_id, current_user.username, "APPROVAL_SIGNED",
        f"전자서명 등록 (hash={sig_hash[:8]}...)"
    )
    await db.commit()
    return {"approval_id": approval_id, "signed": True, "signer": current_user.username}


@router.get("/pending/overdue")
async def overdue_approvals(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """기한 초과 대기 승인 목록 — PM/ADMIN만 접근 가능."""
    from models import UserRole
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM 또는 ADMIN만 접근할 수 있습니다.")

    now = datetime.now()
    rows = (await db.execute(
        select(ApprovalFlow).where(
            ApprovalFlow.result == ApprovalResult.PENDING,
            ApprovalFlow.deadline_at.isnot(None),
            ApprovalFlow.deadline_at < now,
        ).order_by(ApprovalFlow.deadline_at)
    )).scalars().all()

    return [
        {
            "id":            r.id,
            "sr_id":         r.sr_id,
            "approver":      r.approver,
            "deadline_at":   r.deadline_at.isoformat(),
            "overdue_hours": round((now - r.deadline_at).total_seconds() / 3600, 1),
            "delegate_to":   r.delegate_to,
        }
        for r in rows
    ]


@router.post("/{approval_id}/extend-deadline")
async def extend_deadline(
    approval_id: int,
    body: ExtendDeadlineRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """승인 마감 시간 연장 (PM/ADMIN 전용)."""
    from models import UserRole
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM 또는 ADMIN만 마감을 연장할 수 있습니다.")

    apv = await db.get(ApprovalFlow, approval_id)
    if not apv:
        raise HTTPException(404, "승인 레코드를 찾을 수 없습니다.")
    if apv.result != ApprovalResult.PENDING:
        raise HTTPException(400, "대기 중인 결재만 마감 연장 가능합니다.")

    try:
        new_deadline = datetime.fromisoformat(body.new_deadline)
    except ValueError:
        raise HTTPException(400, "new_deadline 필드는 ISO 날짜시간 형식이어야 합니다.")

    old_deadline = apv.deadline_at
    apv.deadline_at = new_deadline
    await _write_audit(
        db, apv.sr_id, current_user.username, "APPROVAL_DEADLINE_EXTENDED",
        f"마감 연장: {old_deadline} → {new_deadline} | 사유: {body.reason or ''}"
    )
    await db.commit()
    return {
        "approval_id":   approval_id,
        "new_deadline":  body.new_deadline,
        "message":       "승인 마감이 연장되었습니다.",
    }


# ════════════════════════════════════════════════════════════════════════════════
# ── 모바일 100기능: 다단계 승인 현황 ────────────────────────────────────────────
# ════════════════════════════════════════════════════════════════════════════════

@router.get("/{approval_id}/stages")
async def approval_stages(
    approval_id: int,
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """다단계 승인 현황 — 동일 SR의 모든 승인 단계를 순서대로 반환 (#67)."""
    apv = await db.get(ApprovalFlow, approval_id)
    if not apv:
        raise HTTPException(404, "승인 레코드를 찾을 수 없습니다.")

    rows = (await db.execute(
        select(ApprovalFlow).where(ApprovalFlow.sr_id == apv.sr_id)
        .order_by(ApprovalFlow.created_at, ApprovalFlow.id)
    )).scalars().all()

    stages = []
    for level, r in enumerate(rows, start=1):
        stages.append({
            "level": level,
            "approval_id": r.id,
            "approver": r.approver,
            "status": r.result,
            "approved_at": r.decided_at.isoformat() if r.decided_at else None,
            "delegate_to": r.delegate_to,
            "signed": bool(r.signature),
        })
    return {"sr_id": apv.sr_id, "stages": stages, "total_stages": len(stages)}


# ════════════════════════════════════════════════════════════════════════════════
# ── 변경 달력 (#68) — /api/changes/calendar ─────────────────────────────────────
# ════════════════════════════════════════════════════════════════════════════════

changes_router = APIRouter(prefix="/api/changes", tags=["changes"])


@changes_router.get("/calendar")
async def changes_calendar(
    month: str,   # YYYY-MM
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """변경 달력 — 해당 월 날짜별 변경(SR) 목록 (#68)."""
    try:
        year, mon = month.split("-")
        year_i, mon_i = int(year), int(mon)
        if not (1 <= mon_i <= 12):
            raise ValueError()
    except (ValueError, AttributeError):
        raise HTTPException(422, "month는 YYYY-MM 형식이어야 합니다.")

    from datetime import date as _date
    start = _date(year_i, mon_i, 1)
    end = _date(year_i + (1 if mon_i == 12 else 0),
                1 if mon_i == 12 else mon_i + 1, 1)
    start_dt = datetime(start.year, start.month, start.day)
    end_dt = datetime(end.year, end.month, end.day)

    # CHANGE / DEPLOY 유형 SR을 변경으로 취급, created_at 기준 그룹핑
    from models import SRType
    q = select(SRRequest).where(
        SRRequest.created_at >= start_dt,
        SRRequest.created_at < end_dt,
    )
    # CUSTOMER 기관 필터
    from models import UserRole
    if current_user.role == UserRole.CUSTOMER and current_user.inst_code:
        from models import Institution as _Inst
        inst = (await db.execute(
            select(_Inst).where(_Inst.inst_code == current_user.inst_code)
        )).scalars().first()
        q = q.where(SRRequest.inst_id == (inst.id if inst else -1))

    srs = (await db.execute(q.order_by(SRRequest.created_at))).scalars().all()

    by_date: dict = {}
    for s in srs:
        if not s.created_at:
            continue
        key = s.created_at.date().isoformat()
        by_date.setdefault(key, []).append({
            "sr_id": s.sr_id,
            "title": s.title,
            "sr_type": s.sr_type,
            "status": s.status,
            "priority": s.priority,
        })

    return {"month": month, "calendar": by_date, "total": len(srs)}
