"""
온콜/당직 일정 관리 API.

엔드포인트:
  GET    /api/oncall              — 당직 일정 목록 (기간 필터)
  POST   /api/oncall              — 당직 등록
  GET    /api/oncall/today        — 오늘의 당직자 (장애 시 즉시 조회용)
  GET    /api/oncall/week         — 이번 주 당직 일정
  PATCH  /api/oncall/{id}         — 당직 수정
  DELETE /api/oncall/{id}         — 당직 삭제
  POST   /api/oncall/bulk         — 일괄 등록 (주간/월간 당직표)
"""
from __future__ import annotations

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

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

from core.auth import get_current_user
from database import get_db
from models import (
    OncallSchedule, OncallScheduleCreate, OncallScheduleOut,
    OncallRotateConfig, OncallRotateConfigOut, OncallRotateConfigUpdate,
    User, UserRole,
)

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


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

@router.get("", response_model=List[OncallScheduleOut])
async def list_oncall(
    date_from: Optional[date] = Query(None),
    date_to:   Optional[date] = Query(None),
    engineer:  Optional[str]  = Query(None),
    shift:     Optional[str]  = Query(None),
    skip: int = 0,
    limit: int = 100,
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    q = select(OncallSchedule)
    if date_from:
        q = q.where(OncallSchedule.duty_date >= date_from)
    if date_to:
        q = q.where(OncallSchedule.duty_date <= date_to)
    if engineer:
        q = q.where(OncallSchedule.engineer.contains(engineer))
    if shift:
        q = q.where(OncallSchedule.shift == shift)
    q = q.order_by(OncallSchedule.duty_date, OncallSchedule.shift).offset(skip).limit(limit)
    result = await db.execute(q)
    return result.scalars().all()


# ── 오늘 당직 ─────────────────────────────────────────────────────────────────

@router.get("/today")
async def today_oncall(
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """오늘의 당직자 조회 — 장애 발생 시 즉시 연락처 확인용."""
    today = date.today()
    result = await db.execute(
        select(OncallSchedule)
        .where(OncallSchedule.duty_date == today)
        .order_by(OncallSchedule.shift)
    )
    schedules = result.scalars().all()

    if not schedules:
        return {
            "date":       today.isoformat(),
            "schedules":  [],
            "message":    "오늘 등록된 당직자가 없습니다.",
        }

    return {
        "date":      today.isoformat(),
        "schedules": [
            {
                "id":               s.id,
                "shift":            s.shift,
                "engineer":         s.engineer,
                "backup_engineer":  s.backup_engineer,
                "escalation_to":    s.escalation_to,
                "note":             s.note,
            }
            for s in schedules
        ],
    }


# ── 이번 주 ───────────────────────────────────────────────────────────────────

@router.get("/week")
async def week_oncall(
    week_offset: int = Query(0, description="0=이번주, 1=다음주, -1=지난주"),
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """이번 주 (월~일) 당직 일정 캘린더 형식."""
    today = date.today()
    monday = today - timedelta(days=today.weekday()) + timedelta(weeks=week_offset)
    sunday = monday + timedelta(days=6)

    result = await db.execute(
        select(OncallSchedule)
        .where(
            OncallSchedule.duty_date >= monday,
            OncallSchedule.duty_date <= sunday,
        )
        .order_by(OncallSchedule.duty_date, OncallSchedule.shift)
    )
    schedules = result.scalars().all()

    # 날짜별 그룹핑
    by_date: dict[str, list] = {}
    for d_offset in range(7):
        d = monday + timedelta(days=d_offset)
        by_date[d.isoformat()] = []
    for s in schedules:
        key = s.duty_date.isoformat()
        if key in by_date:
            by_date[key].append({
                "id":              s.id,
                "shift":           s.shift,
                "engineer":        s.engineer,
                "backup_engineer": s.backup_engineer,
                "escalation_to":   s.escalation_to,
            })

    return {
        "week_start": monday.isoformat(),
        "week_end":   sunday.isoformat(),
        "schedule":   by_date,
    }


# ── 등록 ──────────────────────────────────────────────────────────────────────

@router.post("", response_model=OncallScheduleOut, status_code=201)
async def create_oncall(
    payload: OncallScheduleCreate,
    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, "ADMIN 또는 PM 권한이 필요합니다.")

    # 중복 확인 (같은 날 + 같은 shift)
    dup = (await db.execute(
        select(OncallSchedule).where(
            OncallSchedule.duty_date == payload.duty_date,
            OncallSchedule.shift     == payload.shift,
        )
    )).scalars().first()
    if dup:
        raise HTTPException(
            409,
            f"{payload.duty_date.isoformat()} {payload.shift} 시프트가 이미 등록되어 있습니다. "
            f"수정이 필요하면 PATCH /{dup.id}를 사용하세요.",
        )

    schedule = OncallSchedule(
        **payload.model_dump(),
        created_by=current_user.username,
    )
    db.add(schedule)
    await db.commit()
    await db.refresh(schedule)
    return schedule


# ── 일괄 등록 ─────────────────────────────────────────────────────────────────

@router.post("/bulk", status_code=201)
async def bulk_create_oncall(
    items: List[OncallScheduleCreate],
    overwrite: bool = Query(False, description="기존 일정 덮어쓰기 여부"),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """주간/월간 당직표 일괄 등록 (최대 62건)."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "ADMIN 또는 PM 권한이 필요합니다.")
    if len(items) > 62:
        raise HTTPException(422, "한 번에 최대 62건까지 등록 가능합니다.")

    created = 0
    skipped = 0
    for payload in items:
        existing = (await db.execute(
            select(OncallSchedule).where(
                OncallSchedule.duty_date == payload.duty_date,
                OncallSchedule.shift     == payload.shift,
            )
        )).scalars().first()

        if existing:
            if overwrite:
                for k, v in payload.model_dump(exclude_unset=True).items():
                    setattr(existing, k, v)
                existing.updated_at = datetime.now()
                created += 1
            else:
                skipped += 1
            continue

        schedule = OncallSchedule(
            **payload.model_dump(),
            created_by=current_user.username,
        )
        db.add(schedule)
        created += 1

    await db.commit()
    return {
        "created": created,
        "skipped": skipped,
        "message": f"{created}건 등록, {skipped}건 건너뜀",
    }


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

@router.patch("/{oncall_id}", response_model=OncallScheduleOut)
async def update_oncall(
    oncall_id: int,
    payload: OncallScheduleCreate,
    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, "ADMIN 또는 PM 권한이 필요합니다.")

    r = await db.execute(select(OncallSchedule).where(OncallSchedule.id == oncall_id))
    sched = r.scalars().first()
    if not sched:
        raise HTTPException(404, "당직 일정을 찾을 수 없습니다.")

    for k, v in payload.model_dump(exclude_unset=True).items():
        setattr(sched, k, v)
    sched.updated_at = datetime.now()
    await db.commit()
    await db.refresh(sched)
    return sched


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

@router.delete("/{oncall_id}", status_code=204)
async def delete_oncall(
    oncall_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, "ADMIN 또는 PM 권한이 필요합니다.")

    r = await db.execute(select(OncallSchedule).where(OncallSchedule.id == oncall_id))
    sched = r.scalars().first()
    if not sched:
        raise HTTPException(404, "당직 일정을 찾을 수 없습니다.")

    await db.delete(sched)
    await db.commit()


# ═══════════════════════════════════════════════════════════════════════════════
# ── A-5: On-Call 자동 로테이션 엔드포인트 ────────────────────────────────────
# ═══════════════════════════════════════════════════════════════════════════════

@router.get("/rotate/config", response_model=OncallRotateConfigOut)
async def get_rotate_config(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    On-Call 자동 로테이션 설정 조회.
    설정이 없으면 기본값으로 생성 후 반환.
    """
    from core.oncall_rotate import get_or_create_rotate_config
    cfg = await get_or_create_rotate_config(db)
    return cfg


@router.put("/rotate/config", response_model=OncallRotateConfigOut)
async def update_rotate_config(
    payload: OncallRotateConfigUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    On-Call 자동 로테이션 설정 수정.
    ADMIN / PM 권한 필요.
    """
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "ADMIN 또는 PM 권한이 필요합니다.")

    from core.oncall_rotate import get_or_create_rotate_config
    cfg = await get_or_create_rotate_config(db)

    if payload.is_active is not None:
        cfg.is_active = payload.is_active
    if payload.engineer_list is not None:
        if len(payload.engineer_list) > 50:
            raise HTTPException(422, "엔지니어 목록은 최대 50명까지 등록 가능합니다.")
        cfg.engineer_list = json.dumps(payload.engineer_list, ensure_ascii=False)
        # current_index 범위 조정
        if cfg.current_index >= len(payload.engineer_list):
            cfg.current_index = 0
    if payload.current_index is not None:
        eng_count = len(json.loads(cfg.engineer_list or "[]"))
        if eng_count > 0 and payload.current_index >= eng_count:
            raise HTTPException(422, f"current_index는 0~{eng_count-1} 범위여야 합니다.")
        cfg.current_index = payload.current_index
    if payload.rotate_days is not None:
        cfg.rotate_days = max(1, payload.rotate_days)
    if payload.default_shift is not None:
        allowed_shifts = {"ALL_DAY", "DAYTIME", "NIGHTTIME"}
        if payload.default_shift not in allowed_shifts:
            raise HTTPException(422, f"shift는 {allowed_shifts} 중 하나여야 합니다.")
        cfg.default_shift = payload.default_shift
    if payload.escalation_chain is not None:
        cfg.escalation_chain = json.dumps(payload.escalation_chain, ensure_ascii=False)
    if payload.notify_on_assign is not None:
        cfg.notify_on_assign = payload.notify_on_assign
    if payload.advance_days is not None:
        cfg.advance_days = max(0, payload.advance_days)

    cfg.updated_at = datetime.now()
    cfg.updated_by = current_user.username
    await db.commit()
    await db.refresh(cfg)
    return cfg


@router.get("/on-duty")
async def get_on_duty(
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """
    현재 당직자 + 에스컬레이션 체인 전체 조회.
    장애 발생 시 연락해야 할 순서를 즉시 파악하기 위한 엔드포인트.
    """
    from core.oncall_rotate import get_current_oncall, _get_rotate_config
    from models import OncallRotateConfig

    today = date.today()
    schedule = await get_current_oncall(db)
    cfg = await _get_rotate_config(db)

    # 에스컬레이션 체인 구성
    chain: List[str] = []
    if schedule:
        if schedule.engineer:
            chain.append(schedule.engineer)
        if schedule.backup_engineer:
            chain.append(schedule.backup_engineer)
        if schedule.escalation_to:
            chain.append(schedule.escalation_to)

    # 로테이션 설정의 escalation_chain 추가
    if cfg and cfg.escalation_chain:
        extra = json.loads(cfg.escalation_chain)
        for e in extra:
            if e not in chain:
                chain.append(e)

    return {
        "date":               today.isoformat(),
        "has_schedule":       schedule is not None,
        "primary_engineer":   schedule.engineer if schedule else None,
        "backup_engineer":    schedule.backup_engineer if schedule else None,
        "shift":              schedule.shift if schedule else None,
        "escalation_chain":   chain,
        "rotate_active":      cfg.is_active if cfg else False,
        "note":               schedule.note if schedule else None,
    }


@router.post("/escalate")
async def trigger_escalation(
    incident_title: str = Query(..., description="인시던트/장애 제목"),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    수동 에스컬레이션 트리거.
    ADMIN / PM / ENGINEER 가 즉시 당직 에스컬레이션 체인 실행.

    당직자 → 백업 → escalation_to → ADMIN 순으로 메신저 알림 발송.
    """
    if current_user.role == UserRole.CUSTOMER:
        raise HTTPException(403, "권한이 없습니다.")

    from core.oncall_rotate import escalate_oncall
    target = await escalate_oncall(incident_title, db)

    return {
        "escalated_to":   target,
        "incident_title": incident_title,
        "triggered_by":   current_user.username,
        "triggered_at":   datetime.now().isoformat(),
        "message":        f"에스컬레이션 알림을 '{target}'에게 발송했습니다." if target
                          else "에스컬레이션 대상을 찾을 수 없습니다.",
    }


@router.post("/rotate/trigger", status_code=201)
async def manual_rotate_trigger(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    수동 로테이션 트리거 (테스트/긴급 재배정용).
    ADMIN 전용 — 자동 로테이션 로직을 즉시 한 번 실행.
    """
    if current_user.role != UserRole.ADMIN:
        raise HTTPException(403, "ADMIN 권한이 필요합니다.")

    from core.oncall_rotate import auto_rotate_oncall
    await auto_rotate_oncall()

    return {
        "message":      "수동 로테이션 실행 완료",
        "triggered_by": current_user.username,
        "triggered_at": datetime.now().isoformat(),
    }
