"""
C-2: 변경 관리 CAB (Change Advisory Board) API 라우터

엔드포인트:
  POST /api/change/rfc              — RFC 생성
  GET  /api/change/rfc              — RFC 목록
  GET  /api/change/rfc/{rfc_id}     — RFC 상세
  PATCH /api/change/rfc/{rfc_id}    — RFC 수정
  POST /api/change/rfc/{rfc_id}/submit  — CAB 검토 제출
  POST /api/change/rfc/{rfc_id}/vote    — CAB 투표
  POST /api/change/rfc/{rfc_id}/decide  — CAB 최종 결정 (승인/거부)
  POST /api/change/rfc/{rfc_id}/schedule — 일정 확정
  POST /api/change/rfc/{rfc_id}/start   — 변경 시작
  POST /api/change/rfc/{rfc_id}/complete — 변경 완료
  POST /api/change/rfc/{rfc_id}/fail    — 변경 실패/롤백
  GET  /api/change/rfc/{rfc_id}/votes   — CAB 투표 현황
  POST /api/change/freeze           — 동결 기간 등록
  GET  /api/change/freeze           — 동결 기간 목록
  DELETE /api/change/freeze/{id}    — 동결 기간 삭제
  GET  /api/change/freeze/check     — 특정 일시 동결 여부 확인
  GET  /api/change/calendar         — 변경 일정 캘린더
  GET  /api/change/stats            — 변경 관리 통계
"""
from __future__ import annotations

import json
import logging
from datetime import datetime, timedelta
from typing import List, Optional

from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, Body
from pydantic import BaseModel
from sqlalchemy import select, desc, func, and_, or_
from sqlalchemy.ext.asyncio import AsyncSession

from core.auth import get_current_user
from database import get_db
from models import (
    User, UserRole,
    RFChange, RFChangeOut, RFChangeCreate, RFChangeUpdate,
    CABVote, CABVoteOut, CABVoteCreate,
    FreezeWindow, FreezeWindowOut, FreezeWindowCreate,
    RFCStatus, ChangeType, ChangeRisk, CABVoteResult,
)

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/change", tags=["change"])


# ── 유틸리티 ─────────────────────────────────────────────────────────────────

async def _next_rfc_id(db: AsyncSession) -> str:
    """RFC-YYYYMMDD-NNNN 형식 RFC ID 생성."""
    today  = datetime.utcnow().strftime("%Y%m%d")
    prefix = f"RFC-{today}-"
    last   = (await db.execute(
        select(RFChange.rfc_id)
        .where(RFChange.rfc_id.like(f"{prefix}%"))
        .order_by(desc(RFChange.rfc_id))
        .limit(1)
    )).scalar()
    seq = 1
    if last:
        try:
            seq = int(last.split("-")[-1]) + 1
        except ValueError:
            seq = 1
    return f"{prefix}{seq:04d}"


async def _check_freeze(db: AsyncSession, start_dt: datetime, end_dt: datetime) -> Optional[FreezeWindow]:
    """변경 일정이 동결 기간과 겹치는지 확인."""
    freezes = (await db.execute(
        select(FreezeWindow).where(
            and_(
                FreezeWindow.is_active == True,
                FreezeWindow.start_dt <= end_dt,
                FreezeWindow.end_dt   >= start_dt,
            )
        )
    )).scalars().all()
    return freezes[0] if freezes else None


async def _get_rfc(db: AsyncSession, rfc_id: str) -> RFChange:
    """RFC 조회 + 404 처리."""
    rfc = (await db.execute(
        select(RFChange).where(RFChange.rfc_id == rfc_id)
    )).scalars().first()
    if not rfc:
        raise HTTPException(404, f"RFC {rfc_id}를 찾을 수 없습니다.")
    return rfc


def _count_votes(votes: List[CABVote]) -> dict:
    """투표 집계."""
    result = {"APPROVE": 0, "REJECT": 0, "ABSTAIN": 0, "DEFER": 0}
    for v in votes:
        result[v.vote] = result.get(v.vote, 0) + 1
    return result


# ── RFC CRUD ────────────────────────────────────────────────────────────────

@router.post("/rfc", response_model=RFChangeOut, status_code=201)
async def create_rfc(
    body: RFChangeCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """변경 요청서(RFC) 생성."""
    rfc_id = await _next_rfc_id(db)
    rfc = RFChange(
        rfc_id          = rfc_id,
        title           = body.title,
        description     = body.description,
        change_type     = body.change_type.upper(),
        risk_level      = body.risk_level.upper(),
        status          = RFCStatus.DRAFT.value,
        priority        = body.priority.upper(),
        planned_start   = body.planned_start,
        planned_end     = body.planned_end,
        freeze_exempt   = body.freeze_exempt,
        change_plan     = body.change_plan,
        rollback_plan   = body.rollback_plan,
        test_plan       = body.test_plan,
        impact_analysis = body.impact_analysis,
        sr_id           = body.sr_id,
        ci_ids_json     = json.dumps(body.ci_ids or [], ensure_ascii=False),
        requester_id    = current_user.id,
        created_by      = current_user.username,
    )
    db.add(rfc)
    await db.commit()
    await db.refresh(rfc)
    return rfc


@router.get("/rfc", response_model=List[RFChangeOut])
async def list_rfc(
    status:       Optional[str] = Query(None),
    change_type:  Optional[str] = Query(None),
    risk_level:   Optional[str] = Query(None),
    created_by:   Optional[str] = Query(None),
    hours:        int = Query(720, ge=1, le=8760),
    limit:        int = Query(20, ge=1, le=100),
    offset:       int = Query(0, ge=0),
    db:           AsyncSession = Depends(get_db),
    _u:           User = Depends(get_current_user),
):
    """RFC 목록 조회."""
    since = datetime.utcnow() - timedelta(hours=hours)
    conditions = [RFChange.created_at >= since]

    if status:
        conditions.append(RFChange.status == status.upper())
    if change_type:
        conditions.append(RFChange.change_type == change_type.upper())
    if risk_level:
        conditions.append(RFChange.risk_level == risk_level.upper())
    if created_by:
        conditions.append(RFChange.created_by == created_by)

    q = (
        select(RFChange)
        .where(and_(*conditions))
        .order_by(desc(RFChange.created_at))
        .limit(limit).offset(offset)
    )
    return (await db.execute(q)).scalars().all()


@router.get("/rfc/{rfc_id}", response_model=RFChangeOut)
async def get_rfc(
    rfc_id: str,
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """RFC 상세 조회."""
    return await _get_rfc(db, rfc_id)


@router.patch("/rfc/{rfc_id}", response_model=RFChangeOut)
async def update_rfc(
    rfc_id: str,
    body: RFChangeUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """RFC 수정 (DRAFT / REJECTED 상태에서만 가능)."""
    rfc = await _get_rfc(db, rfc_id)
    if rfc.status not in (RFCStatus.DRAFT.value, RFCStatus.REJECTED.value):
        raise HTTPException(400, f"수정 불가 상태: {rfc.status}")

    for field, val in body.model_dump(exclude_unset=True, exclude_none=True).items():
        if field == "ci_ids":
            rfc.ci_ids_json = json.dumps(val, ensure_ascii=False)
        else:
            if hasattr(rfc, field):
                setattr(rfc, field, val)

    await db.commit()
    await db.refresh(rfc)
    return rfc


# ── RFC 상태 전환 ─────────────────────────────────────────────────────────────

@router.post("/rfc/{rfc_id}/submit")
async def submit_rfc(
    rfc_id: str,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """RFC를 CAB 검토에 제출 (DRAFT → SUBMITTED)."""
    rfc = await _get_rfc(db, rfc_id)
    if rfc.status != RFCStatus.DRAFT.value:
        raise HTTPException(400, f"DRAFT 상태가 아닙니다: {rfc.status}")

    # 필수 항목 체크
    if not rfc.change_plan:
        raise HTTPException(400, "변경 계획(change_plan)이 필요합니다.")
    if not rfc.rollback_plan:
        raise HTTPException(400, "롤백 계획(rollback_plan)이 필요합니다.")

    rfc.status       = RFCStatus.SUBMITTED.value
    rfc.submitted_at = datetime.utcnow()
    await db.commit()
    return {
        "rfc_id":  rfc_id,
        "status":  rfc.status,
        "message": "CAB 검토 대기열에 등록되었습니다.",
    }


@router.post("/rfc/{rfc_id}/vote")
async def cast_vote(
    rfc_id: str,
    body: CABVoteCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """CAB 위원 투표."""
    rfc = await _get_rfc(db, rfc_id)
    if rfc.status not in (RFCStatus.SUBMITTED.value, RFCStatus.IN_REVIEW.value):
        raise HTTPException(400, f"투표 불가 상태: {rfc.status}")

    vote_val = body.vote.upper()
    if vote_val not in [v.value for v in CABVoteResult]:
        raise HTTPException(400, f"유효하지 않은 투표: {vote_val}")

    # 동일 투표자 중복 투표 검사
    existing = (await db.execute(
        select(CABVote).where(
            and_(
                CABVote.rfc_id_fk == rfc.id,
                CABVote.voter_id  == current_user.id,
            )
        )
    )).scalars().first()
    if existing:
        # 투표 변경 허용
        existing.vote    = vote_val
        existing.comment = body.comment
        existing.voted_at = datetime.utcnow()
        await db.commit()
        return {"message": "투표가 변경되었습니다.", "vote": vote_val}

    # 신규 투표
    vote = CABVote(
        rfc_id_fk  = rfc.id,
        voter_id   = current_user.id,
        voter_name = current_user.username,
        vote       = vote_val,
        comment    = body.comment,
        is_final   = body.is_final,
    )
    db.add(vote)

    # SUBMITTED → IN_REVIEW 전환 (첫 투표 시)
    if rfc.status == RFCStatus.SUBMITTED.value:
        rfc.status = RFCStatus.IN_REVIEW.value

    await db.commit()
    return {"message": "투표가 등록되었습니다.", "vote": vote_val}


@router.post("/rfc/{rfc_id}/decide")
async def decide_rfc(
    rfc_id: str,
    approve: bool = Body(..., embed=True),
    note: Optional[str] = Body(None, embed=True),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """CAB 최종 결정 (승인/거부). PM 또는 ADMIN 권한 필요."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "CAB 최종 결정은 PM/ADMIN 권한이 필요합니다.")

    rfc = await _get_rfc(db, rfc_id)
    if rfc.status not in (RFCStatus.IN_REVIEW.value, RFCStatus.SUBMITTED.value):
        raise HTTPException(400, f"결정 불가 상태: {rfc.status}")

    # 최종 투표 기록
    vote = CABVote(
        rfc_id_fk  = rfc.id,
        voter_id   = current_user.id,
        voter_name = current_user.username,
        vote       = CABVoteResult.APPROVE.value if approve else CABVoteResult.REJECT.value,
        comment    = note or ("최종 승인" if approve else "최종 거부"),
        is_final   = True,
    )
    db.add(vote)

    if approve:
        rfc.status      = RFCStatus.APPROVED.value
        rfc.approved_at = datetime.utcnow()
        msg = f"RFC {rfc_id} 승인 완료"
    else:
        rfc.status = RFCStatus.REJECTED.value
        msg = f"RFC {rfc_id} 거부됨"

    await db.commit()
    return {"rfc_id": rfc_id, "approved": approve, "status": rfc.status, "message": msg}


@router.post("/rfc/{rfc_id}/schedule")
async def schedule_rfc(
    rfc_id: str,
    planned_start: datetime = Body(..., embed=True),
    planned_end:   datetime = Body(..., embed=True),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """RFC 일정 확정 (APPROVED → SCHEDULED). 동결 기간 충돌 검사."""
    rfc = await _get_rfc(db, rfc_id)
    if rfc.status != RFCStatus.APPROVED.value:
        raise HTTPException(400, f"APPROVED 상태가 아닙니다: {rfc.status}")

    # 동결 기간 충돌 검사
    if not rfc.freeze_exempt:
        freeze = await _check_freeze(db, planned_start, planned_end)
        if freeze:
            raise HTTPException(409, {
                "message":    f"변경 동결 기간과 충돌합니다: {freeze.name}",
                "freeze_id":  freeze.id,
                "freeze_name": freeze.name,
                "freeze_start": freeze.start_dt.isoformat(),
                "freeze_end":   freeze.end_dt.isoformat(),
            })

    rfc.planned_start = planned_start
    rfc.planned_end   = planned_end
    rfc.status        = RFCStatus.SCHEDULED.value
    await db.commit()
    return {
        "rfc_id":        rfc_id,
        "status":        rfc.status,
        "planned_start": planned_start.isoformat(),
        "planned_end":   planned_end.isoformat(),
    }


@router.post("/rfc/{rfc_id}/start")
async def start_rfc(
    rfc_id: str,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """변경 실행 시작 (SCHEDULED → IN_PROGRESS)."""
    rfc = await _get_rfc(db, rfc_id)
    if rfc.status not in (RFCStatus.SCHEDULED.value, RFCStatus.APPROVED.value):
        raise HTTPException(400, f"시작 불가 상태: {rfc.status}")

    rfc.status       = RFCStatus.IN_PROGRESS.value
    rfc.actual_start = datetime.utcnow()
    await db.commit()
    return {"rfc_id": rfc_id, "status": rfc.status}


@router.post("/rfc/{rfc_id}/complete")
async def complete_rfc(
    rfc_id: str,
    result_summary: Optional[str] = Body(None, embed=True),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """변경 완료 (IN_PROGRESS → COMPLETED)."""
    rfc = await _get_rfc(db, rfc_id)
    if rfc.status != RFCStatus.IN_PROGRESS.value:
        raise HTTPException(400, f"IN_PROGRESS 상태가 아닙니다: {rfc.status}")

    rfc.status         = RFCStatus.COMPLETED.value
    rfc.actual_end     = datetime.utcnow()
    rfc.completed_at   = datetime.utcnow()
    if result_summary:
        rfc.result_summary = result_summary
    await db.commit()
    return {"rfc_id": rfc_id, "status": rfc.status}


@router.post("/rfc/{rfc_id}/fail")
async def fail_rfc(
    rfc_id: str,
    error_msg: Optional[str] = Body(None, embed=True),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """변경 실패/롤백 처리 (IN_PROGRESS → FAILED)."""
    rfc = await _get_rfc(db, rfc_id)
    if rfc.status != RFCStatus.IN_PROGRESS.value:
        raise HTTPException(400, f"IN_PROGRESS 상태가 아닙니다: {rfc.status}")

    rfc.status     = RFCStatus.FAILED.value
    rfc.actual_end = datetime.utcnow()
    if error_msg:
        rfc.error_msg = error_msg
    await db.commit()
    return {"rfc_id": rfc_id, "status": rfc.status}


# ── CAB 투표 현황 ─────────────────────────────────────────────────────────────

@router.get("/rfc/{rfc_id}/votes")
async def get_votes(
    rfc_id: str,
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """RFC CAB 투표 현황."""
    rfc = await _get_rfc(db, rfc_id)
    votes = (await db.execute(
        select(CABVote)
        .where(CABVote.rfc_id_fk == rfc.id)
        .order_by(CABVote.voted_at)
    )).scalars().all()

    tally = _count_votes(votes)
    total = sum(tally.values())

    return {
        "rfc_id":  rfc_id,
        "status":  rfc.status,
        "tally":   tally,
        "total":   total,
        "approved": tally["APPROVE"] > (total / 2) if total > 0 else False,
        "votes":   [
            {
                "id":         v.id,
                "voter":      v.voter_name,
                "vote":       v.vote,
                "comment":    v.comment,
                "is_final":   v.is_final,
                "voted_at":   v.voted_at.isoformat(),
            }
            for v in votes
        ],
    }


# ── 동결 기간 관리 ────────────────────────────────────────────────────────────

@router.post("/freeze", response_model=FreezeWindowOut, status_code=201)
async def create_freeze(
    body: FreezeWindowCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """변경 동결 기간 등록. PM/ADMIN 권한 필요."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM/ADMIN 권한이 필요합니다.")
    if body.start_dt >= body.end_dt:
        raise HTTPException(400, "종료 일시가 시작 일시보다 늦어야 합니다.")

    freeze = FreezeWindow(
        name       = body.name,
        start_dt   = body.start_dt,
        end_dt     = body.end_dt,
        scope      = body.scope.upper(),
        reason     = body.reason,
        created_by = current_user.username,
    )
    db.add(freeze)
    await db.commit()
    await db.refresh(freeze)
    return freeze


@router.get("/freeze", response_model=List[FreezeWindowOut])
async def list_freeze(
    active_only: bool = Query(True),
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """동결 기간 목록."""
    q = select(FreezeWindow)
    if active_only:
        q = q.where(FreezeWindow.is_active == True)
    q = q.order_by(FreezeWindow.start_dt)
    return (await db.execute(q)).scalars().all()


@router.delete("/freeze/{freeze_id}", status_code=204)
async def delete_freeze(
    freeze_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """동결 기간 비활성화 (삭제 대신 is_active=False)."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM/ADMIN 권한이 필요합니다.")
    freeze = (await db.execute(select(FreezeWindow).where(FreezeWindow.id == freeze_id))).scalars().first()
    if not freeze:
        raise HTTPException(404, "동결 기간을 찾을 수 없습니다.")
    freeze.is_active = False
    await db.commit()


@router.get("/freeze/check")
async def check_freeze(
    start_dt: datetime = Query(...),
    end_dt:   datetime = Query(...),
    db:       AsyncSession = Depends(get_db),
    _u:       User = Depends(get_current_user),
):
    """특정 일시 범위가 동결 기간에 해당하는지 확인."""
    freeze = await _check_freeze(db, start_dt, end_dt)
    if freeze:
        return {
            "frozen":      True,
            "freeze_id":   freeze.id,
            "freeze_name": freeze.name,
            "freeze_start": freeze.start_dt.isoformat(),
            "freeze_end":   freeze.end_dt.isoformat(),
            "scope":        freeze.scope,
        }
    return {"frozen": False}


# ── 변경 일정 캘린더 ──────────────────────────────────────────────────────────

@router.get("/calendar")
async def change_calendar(
    year:  int = Query(..., ge=2020, le=2099),
    month: int = Query(..., ge=1, le=12),
    db:    AsyncSession = Depends(get_db),
    _u:    User = Depends(get_current_user),
):
    """해당 월의 변경 일정 + 동결 기간 캘린더."""
    import calendar
    _, days_in_month = calendar.monthrange(year, month)
    start = datetime(year, month, 1)
    end   = datetime(year, month, days_in_month, 23, 59, 59)

    # 해당 기간 RFC
    rfcs = (await db.execute(
        select(RFChange).where(
            and_(
                or_(
                    and_(RFChange.planned_start >= start, RFChange.planned_start <= end),
                    and_(RFChange.planned_end   >= start, RFChange.planned_end   <= end),
                ),
                RFChange.status.notin_([RFCStatus.DRAFT.value, RFCStatus.WITHDRAWN.value]),
            )
        )
    )).scalars().all()

    # 해당 기간 동결
    freezes = (await db.execute(
        select(FreezeWindow).where(
            and_(
                FreezeWindow.is_active == True,
                FreezeWindow.start_dt <= end,
                FreezeWindow.end_dt   >= start,
            )
        )
    )).scalars().all()

    return {
        "year":  year,
        "month": month,
        "changes": [
            {
                "rfc_id":        r.rfc_id,
                "title":         r.title,
                "status":        r.status,
                "change_type":   r.change_type,
                "risk_level":    r.risk_level,
                "planned_start": r.planned_start.isoformat() if r.planned_start else None,
                "planned_end":   r.planned_end.isoformat()   if r.planned_end   else None,
            }
            for r in rfcs
        ],
        "freeze_windows": [
            {
                "id":    f.id,
                "name":  f.name,
                "start": f.start_dt.isoformat(),
                "end":   f.end_dt.isoformat(),
                "scope": f.scope,
            }
            for f in freezes
        ],
    }


# ── 통계 ─────────────────────────────────────────────────────────────────────

@router.get("/stats")
async def change_stats(
    days: int = Query(30, ge=1, le=365),
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """변경 관리 통계."""
    since = datetime.utcnow() - timedelta(days=days)

    total = (await db.execute(
        select(func.count()).select_from(RFChange).where(RFChange.created_at >= since)
    )).scalar() or 0

    by_status = {}
    for st, cnt in (await db.execute(
        select(RFChange.status, func.count()).where(RFChange.created_at >= since).group_by(RFChange.status)
    )).all():
        by_status[st] = cnt

    by_type = {}
    for ct, cnt in (await db.execute(
        select(RFChange.change_type, func.count()).where(RFChange.created_at >= since).group_by(RFChange.change_type)
    )).all():
        by_type[ct] = cnt

    by_risk = {}
    for rl, cnt in (await db.execute(
        select(RFChange.risk_level, func.count()).where(RFChange.created_at >= since).group_by(RFChange.risk_level)
    )).all():
        by_risk[rl] = cnt

    # 승인율
    approved = by_status.get("APPROVED", 0) + by_status.get("SCHEDULED", 0) + by_status.get("IN_PROGRESS", 0) + by_status.get("COMPLETED", 0)
    rejected = by_status.get("REJECTED", 0)
    decided  = approved + rejected
    approval_rate = round(approved / decided * 100, 1) if decided > 0 else 0.0

    # 성공률
    completed = by_status.get("COMPLETED", 0)
    failed    = by_status.get("FAILED", 0)
    executed  = completed + failed
    success_rate = round(completed / executed * 100, 1) if executed > 0 else 0.0

    # 현재 동결 기간
    now = datetime.utcnow()
    current_freeze = (await db.execute(
        select(FreezeWindow).where(
            and_(
                FreezeWindow.is_active == True,
                FreezeWindow.start_dt <= now,
                FreezeWindow.end_dt   >= now,
            )
        )
    )).scalars().first()

    return {
        "period_days":    days,
        "total_rfc":      total,
        "by_status":      by_status,
        "by_type":        by_type,
        "by_risk":        by_risk,
        "approval_rate":  approval_rate,
        "success_rate":   success_rate,
        "currently_frozen": current_freeze is not None,
        "freeze_name":    current_freeze.name if current_freeze else None,
    }
