"""
C-4: 용량 관리 대시보드 API 라우터

엔드포인트:
  GET  /api/capacity/dashboard         — 전체 용량 대시보드
  POST /api/capacity/plans             — 용량 계획 등록
  GET  /api/capacity/plans             — 용량 계획 목록
  GET  /api/capacity/plans/{id}        — 용량 계획 상세
  PATCH /api/capacity/plans/{id}       — 용량 계획 수정
  DELETE /api/capacity/plans/{id}      — 용량 계획 삭제
  POST /api/capacity/plans/{id}/recalculate — 예측 재계산
  GET  /api/capacity/alerts            — 용량 경보 목록
  GET  /api/capacity/trends/{source}   — 소스별 트렌드
  GET  /api/capacity/stats             — 용량 통계
"""
from __future__ import annotations

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

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

from core.auth import get_current_user
from database import get_db
from models import (
    User, UserRole,
    CapacityPlan, CapacityPlanOut, CapacityPlanCreate,
    CapacityStatus,
)

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


def _calc_forecasts(current: float, growth_rate: float, capacity_max: Optional[float]):
    """성장률 기반 N개월 후 예측값 및 확장 필요 시점 계산."""
    if not current or not growth_rate:
        return None, None, None, None

    monthly_rate = growth_rate / 100.0
    f3  = current * (1 + monthly_rate) ** 3
    f6  = current * (1 + monthly_rate) ** 6
    f12 = current * (1 + monthly_rate) ** 12

    # 확장 필요 시점 (85% 임계)
    expansion_at = None
    if capacity_max and capacity_max > 0 and growth_rate > 0:
        target = capacity_max * 0.85
        if current < target:
            try:
                months = math.log(target / current) / math.log(1 + monthly_rate)
                expansion_at = datetime.utcnow() + timedelta(days=months * 30)
            except (ValueError, ZeroDivisionError):
                pass

    return round(f3, 2), round(f6, 2), round(f12, 2), expansion_at


def _calc_status(current: Optional[float], warn: Optional[float], crit: Optional[float]) -> str:
    if current is None:
        return CapacityStatus.NORMAL.value
    if crit and current >= crit:
        return CapacityStatus.OVERLOAD.value if current >= crit * 1.1 else CapacityStatus.CRITICAL.value
    if warn and current >= warn:
        return CapacityStatus.WARNING.value
    return CapacityStatus.NORMAL.value


@router.get("/dashboard")
async def capacity_dashboard(
    inst_id: Optional[int] = Query(None),
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """전체 용량 관리 대시보드."""
    q = select(CapacityPlan)
    if inst_id:
        q = q.where(CapacityPlan.inst_id == inst_id)

    plans = (await db.execute(q)).scalars().all()

    by_status: dict = {}
    by_metric: dict = {}
    alerts = []

    for p in plans:
        by_status[p.status] = by_status.get(p.status, 0) + 1
        by_metric[p.metric_type] = by_metric.get(p.metric_type, 0) + 1

        if p.status in (CapacityStatus.CRITICAL.value, CapacityStatus.OVERLOAD.value):
            alerts.append({
                "id":           p.id,
                "source":       p.source,
                "metric_type":  p.metric_type,
                "status":       p.status,
                "current":      p.current_value,
                "threshold":    p.threshold_crit,
                "expansion_at": p.expansion_needed_at.isoformat() if p.expansion_needed_at else None,
            })

    return {
        "total_plans":  len(plans),
        "by_status":    by_status,
        "by_metric":    by_metric,
        "alert_count":  len(alerts),
        "alerts":       alerts[:10],  # 최대 10개
        "as_of":        datetime.utcnow().isoformat(),
    }


@router.post("/plans", response_model=CapacityPlanOut, status_code=201)
async def create_plan(
    body: CapacityPlanCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """용량 계획 등록."""
    f3, f6, f12, exp_at = _calc_forecasts(
        body.current_value, body.growth_rate, body.capacity_max
    )
    status = _calc_status(body.current_value, body.threshold_warn, body.threshold_crit)

    plan = CapacityPlan(
        source         = body.source,
        metric_type    = body.metric_type.upper(),
        current_value  = body.current_value,
        capacity_max   = body.capacity_max,
        threshold_warn = body.threshold_warn,
        threshold_crit = body.threshold_crit,
        status         = status,
        growth_rate    = body.growth_rate,
        forecast_3m    = f3,
        forecast_6m    = f6,
        forecast_12m   = f12,
        expansion_needed_at = exp_at,
        note           = body.note,
        inst_id        = body.inst_id,
        updated_by     = current_user.username,
    )
    db.add(plan)
    await db.commit()
    await db.refresh(plan)
    return plan


@router.get("/plans", response_model=List[CapacityPlanOut])
async def list_plans(
    metric_type: Optional[str] = Query(None),
    status:      Optional[str] = Query(None),
    inst_id:     Optional[int] = Query(None),
    limit:       int = Query(50, ge=1, le=200),
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    conditions = []
    if metric_type: conditions.append(CapacityPlan.metric_type == metric_type.upper())
    if status:      conditions.append(CapacityPlan.status      == status.upper())
    if inst_id:     conditions.append(CapacityPlan.inst_id     == inst_id)

    q = select(CapacityPlan)
    if conditions:
        q = q.where(and_(*conditions))
    q = q.order_by(desc(CapacityPlan.updated_at)).limit(limit)
    return (await db.execute(q)).scalars().all()


@router.get("/plans/{plan_id}", response_model=CapacityPlanOut)
async def get_plan(plan_id: int, db: AsyncSession = Depends(get_db), _u: User = Depends(get_current_user)):
    plan = (await db.execute(select(CapacityPlan).where(CapacityPlan.id == plan_id))).scalars().first()
    if not plan:
        raise HTTPException(404, "용량 계획을 찾을 수 없습니다.")
    return plan


@router.patch("/plans/{plan_id}", response_model=CapacityPlanOut)
async def update_plan(
    plan_id: int,
    body: dict = Body(...),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """용량 계획 수정."""
    plan = (await db.execute(select(CapacityPlan).where(CapacityPlan.id == plan_id))).scalars().first()
    if not plan:
        raise HTTPException(404, "용량 계획을 찾을 수 없습니다.")
    for k, v in body.items():
        if hasattr(plan, k) and k not in ("id", "created_at"):
            setattr(plan, k, v)
    plan.status     = _calc_status(plan.current_value, plan.threshold_warn, plan.threshold_crit)
    plan.updated_by = current_user.username
    await db.commit()
    await db.refresh(plan)
    return plan


@router.delete("/plans/{plan_id}", status_code=204)
async def delete_plan(plan_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user)):
    plan = (await db.execute(select(CapacityPlan).where(CapacityPlan.id == plan_id))).scalars().first()
    if not plan:
        raise HTTPException(404, "용량 계획을 찾을 수 없습니다.")
    await db.delete(plan)
    await db.commit()


@router.post("/plans/{plan_id}/recalculate", response_model=CapacityPlanOut)
async def recalculate_plan(
    plan_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """메트릭 이력 기반 예측 재계산."""
    plan = (await db.execute(select(CapacityPlan).where(CapacityPlan.id == plan_id))).scalars().first()
    if not plan:
        raise HTTPException(404, "용량 계획을 찾을 수 없습니다.")

    # 예측 모듈 호출
    try:
        from core.predictive import predict_metric_trend, PREDICTION_THRESHOLDS
        pred = await predict_metric_trend(db, plan.source, plan.metric_type)

        if pred.get("slope_per_hour"):
            monthly_rate = pred["slope_per_hour"] * 24 * 30 / (plan.current_value or 1) * 100
            plan.growth_rate = round(monthly_rate, 2)

        if pred.get("current_value"):
            plan.current_value = pred["current_value"]

    except Exception:
        pass  # 예측 실패 시 기존 값 유지

    f3, f6, f12, exp_at = _calc_forecasts(plan.current_value, plan.growth_rate, plan.capacity_max)
    plan.forecast_3m  = f3
    plan.forecast_6m  = f6
    plan.forecast_12m = f12
    plan.expansion_needed_at = exp_at
    plan.status   = _calc_status(plan.current_value, plan.threshold_warn, plan.threshold_crit)
    plan.updated_by = current_user.username
    await db.commit()
    await db.refresh(plan)
    return plan


@router.get("/alerts")
async def capacity_alerts(
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """CRITICAL/OVERLOAD 상태 용량 경보 목록."""
    plans = (await db.execute(
        select(CapacityPlan)
        .where(CapacityPlan.status.in_([CapacityStatus.CRITICAL.value, CapacityStatus.OVERLOAD.value]))
        .order_by(desc(CapacityPlan.updated_at))
    )).scalars().all()

    return [
        {
            "id":           p.id,
            "source":       p.source,
            "metric_type":  p.metric_type,
            "status":       p.status,
            "current_value": p.current_value,
            "threshold_crit": p.threshold_crit,
            "forecast_3m":  p.forecast_3m,
            "expansion_at": p.expansion_needed_at.isoformat() if p.expansion_needed_at else None,
        }
        for p in plans
    ]


@router.get("/trends/{source}")
async def capacity_trends(
    source: str,
    metric_type: str = Query("CPU_USAGE"),
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """소스별 용량 트렌드 + 예측."""
    try:
        from core.predictive import predict_metric_trend
        pred = await predict_metric_trend(db, source, metric_type.upper())
    except Exception as e:
        pred = {"error": str(e)}

    plan = (await db.execute(
        select(CapacityPlan)
        .where(and_(CapacityPlan.source == source, CapacityPlan.metric_type == metric_type.upper()))
        .order_by(desc(CapacityPlan.updated_at)).limit(1)
    )).scalars().first()

    return {
        "source":       source,
        "metric_type":  metric_type.upper(),
        "prediction":   pred,
        "capacity_plan": {
            "current":    plan.current_value if plan else None,
            "max":        plan.capacity_max  if plan else None,
            "forecast_3m": plan.forecast_3m  if plan else None,
            "growth_rate": plan.growth_rate  if plan else None,
            "expansion_at": plan.expansion_needed_at.isoformat() if plan and plan.expansion_needed_at else None,
        } if plan else None,
    }


@router.get("/stats")
async def capacity_stats(
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """용량 관리 통계."""
    total = (await db.execute(select(func.count()).select_from(CapacityPlan))).scalar() or 0

    by_status: dict = {}
    for st, cnt in (await db.execute(
        select(CapacityPlan.status, func.count()).group_by(CapacityPlan.status)
    )).all():
        by_status[st] = cnt

    # 확장 임박 (30일 이내)
    soon = datetime.utcnow() + timedelta(days=30)
    expansion_soon = (await db.execute(
        select(func.count()).select_from(CapacityPlan)
        .where(and_(CapacityPlan.expansion_needed_at != None, CapacityPlan.expansion_needed_at <= soon))
    )).scalar() or 0

    return {
        "total_plans":        total,
        "by_status":          by_status,
        "critical_count":     by_status.get("CRITICAL", 0) + by_status.get("OVERLOAD", 0),
        "expansion_soon_30d": expansion_soon,
    }
