"""
변경 요청 (Change Request) 관리 API.

엔드포인트:
  GET    /api/si/projects/{pid}/crs         — CR 목록
  POST   /api/si/projects/{pid}/crs         — CR 등록
  GET    /api/si/projects/{pid}/crs/{id}    — CR 상세
  PATCH  /api/si/projects/{pid}/crs/{id}    — CR 수정
  PATCH  /api/si/projects/{pid}/crs/{id}/approve  — CR 승인
  PATCH  /api/si/projects/{pid}/crs/{id}/reject   — CR 반려
  PATCH  /api/si/projects/{pid}/crs/{id}/implement — CR 구현 완료
  DELETE /api/si/projects/{pid}/crs/{id}    — CR 삭제

CR 상태 흐름: DRAFT → REVIEW → APPROVED/REJECTED → IMPLEMENTED
"""
from __future__ import annotations

import logging
from datetime import datetime
from typing import List, Optional
from uuid import uuid4

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

from core.auth import get_current_user
from database import get_db
from models import (
    SiProject, ChangeRequest,
    ChangeRequestCreate, ChangeRequestOut, ChangeRequestUpdate,
    CrStatus,
    User, UserRole,
)

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


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


# ── 목록 ──────────────────────────────────────────────────────────────────────

@router.get("/{project_id}/crs", response_model=List[ChangeRequestOut])
async def list_crs(
    project_id: int,
    cr_type: Optional[str] = Query(None),
    status:  Optional[str] = Query(None),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    await _assert_project(project_id, db)
    q = select(ChangeRequest).where(ChangeRequest.project_id == project_id)
    if cr_type:
        q = q.where(ChangeRequest.cr_type == cr_type)
    if status:
        q = q.where(ChangeRequest.status == status)
    q = q.order_by(desc(ChangeRequest.created_at))
    return (await db.execute(q)).scalars().all()


# ── 생성 ──────────────────────────────────────────────────────────────────────

@router.post("/{project_id}/crs", response_model=ChangeRequestOut, status_code=201)
async def create_cr(
    project_id: int,
    body: ChangeRequestCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    await _assert_project(project_id, db)

    cr = ChangeRequest(
        cr_id           = _new_cr_id(),
        project_id      = project_id,
        cr_type         = body.cr_type,
        title           = body.title,
        description     = body.description,
        reason          = body.reason,
        impact_scope    = body.impact_scope,
        impact_schedule = body.impact_schedule,
        impact_budget   = body.impact_budget,
        requested_by    = body.requested_by or current_user.username,
    )
    db.add(cr)
    await db.commit()
    await db.refresh(cr)
    return cr


# ── 상세 ──────────────────────────────────────────────────────────────────────

@router.get("/{project_id}/crs/{cr_id}", response_model=ChangeRequestOut)
async def get_cr(
    project_id: int, cr_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    return await _get_cr_or_404(project_id, cr_id, db)


# ── 수정 ──────────────────────────────────────────────────────────────────────

@router.patch("/{project_id}/crs/{cr_id}", response_model=ChangeRequestOut)
async def update_cr(
    project_id: int, cr_id: int,
    body: ChangeRequestUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    cr = await _get_cr_or_404(project_id, cr_id, db)

    if cr.status not in (CrStatus.DRAFT, CrStatus.REVIEW):
        raise HTTPException(400, f"'{cr.status}' 상태에서는 수정할 수 없습니다")

    for field, val in body.model_dump(exclude_none=True).items():
        setattr(cr, field, val)

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


# ── 검토 요청 ─────────────────────────────────────────────────────────────────

@router.patch("/{project_id}/crs/{cr_id}/submit-review", response_model=ChangeRequestOut)
async def submit_cr_for_review(
    project_id: int, cr_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    cr = await _get_cr_or_404(project_id, cr_id, db)
    if cr.status != CrStatus.DRAFT:
        raise HTTPException(400, "초안 상태에서만 검토 요청할 수 있습니다")
    cr.status = CrStatus.REVIEW
    await db.commit()
    await db.refresh(cr)
    return cr


# ── 승인 ──────────────────────────────────────────────────────────────────────

@router.patch("/{project_id}/crs/{cr_id}/approve", response_model=ChangeRequestOut)
async def approve_cr(
    project_id: int, cr_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM 이상 권한 필요")
    cr = await _get_cr_or_404(project_id, cr_id, db)
    if cr.status != CrStatus.REVIEW:
        raise HTTPException(400, "검토 중인 CR만 승인할 수 있습니다")

    cr.status      = CrStatus.APPROVED
    cr.approved_by = current_user.username
    cr.approved_at = datetime.now()

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


# ── 반려 ──────────────────────────────────────────────────────────────────────

@router.patch("/{project_id}/crs/{cr_id}/reject", response_model=ChangeRequestOut)
async def reject_cr(
    project_id: int, cr_id: int,
    reason: Optional[str] = Query(None),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM 이상 권한 필요")
    cr = await _get_cr_or_404(project_id, cr_id, db)
    if cr.status not in (CrStatus.REVIEW, CrStatus.DRAFT):
        raise HTTPException(400, "검토 중이거나 초안 상태 CR만 반려할 수 있습니다")

    cr.status      = CrStatus.REJECTED
    cr.reviewed_by = current_user.username
    if reason:
        cr.reason = (cr.reason or "") + f"\n[반려 사유] {reason}"

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


# ── 구현 완료 ─────────────────────────────────────────────────────────────────

@router.patch("/{project_id}/crs/{cr_id}/implement", response_model=ChangeRequestOut)
async def implement_cr(
    project_id: int, cr_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    cr = await _get_cr_or_404(project_id, cr_id, db)
    if cr.status != CrStatus.APPROVED:
        raise HTTPException(400, "승인된 CR만 구현 완료 처리할 수 있습니다")

    cr.status         = CrStatus.IMPLEMENTED
    cr.implemented_at = datetime.now()

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


# ── 삭제 ──────────────────────────────────────────────────────────────────────

@router.delete("/{project_id}/crs/{cr_id}", status_code=204)
async def delete_cr(
    project_id: int, cr_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM 이상 권한 필요")
    cr = await _get_cr_or_404(project_id, cr_id, db)
    await db.delete(cr)
    await db.commit()


# ── 내부 헬퍼 ─────────────────────────────────────────────────────────────────

async def _assert_project(project_id: int, db: AsyncSession) -> None:
    if not (await db.execute(
        select(SiProject).where(SiProject.id == project_id)
    )).scalars().first():
        raise HTTPException(404, f"SI 프로젝트 {project_id}를 찾을 수 없습니다")


async def _get_cr_or_404(project_id: int, cr_id: int, db: AsyncSession) -> ChangeRequest:
    cr = (await db.execute(
        select(ChangeRequest).where(
            ChangeRequest.id == cr_id,
            ChangeRequest.project_id == project_id,
        )
    )).scalars().first()
    if not cr:
        raise HTTPException(404, "변경 요청을 찾을 수 없습니다")
    return cr
