"""
E-5: FinOps 비용 분석

기능:
  1. 서버/인프라 운영 비용 등록 및 조회
  2. 월별·분기별 비용 트렌드 분석
  3. 서비스/부서별 비용 배분 (Cost Allocation)
  4. 비용 이상 탐지 (전월 대비 임계치 초과)
  5. 비용 절감 권고 (미사용·저활용 자원 감지)
  6. 예산 대비 실적 (Budget vs Actual)
  7. Ollama sLLM 기반 비용 최적화 코멘트 생성

엔드포인트:
  POST /api/finops/costs           — 비용 항목 등록
  GET  /api/finops/costs           — 비용 목록 조회
  GET  /api/finops/summary         — 월별 비용 요약
  GET  /api/finops/trend           — 비용 트렌드 (N개월)
  GET  /api/finops/allocation      — 서비스별 비용 배분
  GET  /api/finops/anomalies       — 비용 이상 탐지
  GET  /api/finops/recommendations — 비용 절감 권고
  POST /api/finops/budget          — 예산 등록/수정
  GET  /api/finops/budget          — 예산 대비 실적
  GET  /api/finops/optimize        — AI 비용 최적화 분석
"""
from __future__ import annotations

import logging
from datetime import datetime, date, timedelta
from typing import Dict, List, Optional
from uuid import uuid4

from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession

from core.auth import get_current_user
from database import get_db
from models import User, UserRole

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

# ── 인메모리 저장소 ─────────────────────────────────────────────────────────
_costs: Dict[str, Dict]   = {}   # cost_id -> cost record
_budgets: Dict[str, Dict] = {}   # "YYYY-MM" -> budget record


# ── 비용 카테고리 ────────────────────────────────────────────────────────────
COST_CATEGORIES = {
    "SERVER":      "서버 운영비",
    "NETWORK":     "네트워크/통신비",
    "STORAGE":     "스토리지 비용",
    "LICENSE":     "소프트웨어 라이선스",
    "MAINTENANCE": "유지보수비",
    "PERSONNEL":   "인건비",
    "OTHER":       "기타",
}

SERVICES = ["MES", "ERP", "GROUPWARE", "SECURITY", "INFRA", "BACKUP", "OTHER"]


# ── Pydantic 스키마 ──────────────────────────────────────────────────────────

class CostIn(BaseModel):
    year:       int
    month:      int              # 1~12
    category:   str              # COST_CATEGORIES key
    service:    str = "INFRA"    # SERVICES
    amount:     float            # 원화 (원)
    description: str = ""
    server_id:  Optional[int]  = None
    department: str = "IT"

class BudgetIn(BaseModel):
    year:    int
    month:   int
    amount:  float
    service: str = "ALL"


# ── 헬퍼 ────────────────────────────────────────────────────────────────────

def _gen_cost_id() -> str:
    return f"COST-{datetime.utcnow().strftime('%Y%m%d')}-{uuid4().hex[:6].upper()}"


def _filter_costs(year: int, month: Optional[int] = None,
                  service: Optional[str] = None) -> List[Dict]:
    result = []
    for c in _costs.values():
        if c["year"] != year:
            continue
        if month is not None and c["month"] != month:
            continue
        if service is not None and c["service"] != service:
            continue
        result.append(c)
    return result


def _sum_costs(costs: List[Dict]) -> float:
    return round(sum(c["amount"] for c in costs), 2)


# ── 엔드포인트 ───────────────────────────────────────────────────────────────

@router.post("/costs", status_code=201)
async def create_cost(
    body:         CostIn,
    current_user: User = Depends(get_current_user),
    _db:          AsyncSession = Depends(get_db),
):
    """비용 항목 등록 (PM/ADMIN)."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM/ADMIN 권한이 필요합니다.")
    if body.category not in COST_CATEGORIES:
        raise HTTPException(400, f"유효하지 않은 카테고리: {body.category}. "
                                 f"허용: {list(COST_CATEGORIES)}")
    if not (1 <= body.month <= 12):
        raise HTTPException(400, "월은 1~12 사이여야 합니다.")
    if body.amount < 0:
        raise HTTPException(400, "비용은 0 이상이어야 합니다.")
    if body.service not in SERVICES:
        raise HTTPException(400, f"유효하지 않은 서비스: {body.service}")

    cost_id = _gen_cost_id()
    record = {
        "cost_id":     cost_id,
        "year":        body.year,
        "month":       body.month,
        "category":    body.category,
        "service":     body.service,
        "amount":      body.amount,
        "description": body.description,
        "server_id":   body.server_id,
        "department":  body.department,
        "created_by":  current_user.username,
        "created_at":  datetime.utcnow().isoformat(),
    }
    _costs[cost_id] = record
    logger.info("비용 등록: %s %.0f원 by %s", cost_id, body.amount, current_user.username)
    return record


@router.get("/costs")
async def list_costs(
    year:    int  = Query(...),
    month:   Optional[int]  = Query(None),
    service: Optional[str]  = Query(None),
    current_user: User = Depends(get_current_user),
    _db:     AsyncSession = Depends(get_db),
):
    """비용 목록 조회."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM/ADMIN 권한이 필요합니다.")
    items = _filter_costs(year, month, service)
    return {"total": len(items), "total_amount": _sum_costs(items), "items": items}


@router.get("/summary")
async def cost_summary(
    year:  int = Query(...),
    month: int = Query(...),
    current_user: User = Depends(get_current_user),
    _db:   AsyncSession = Depends(get_db),
):
    """월별 비용 요약 (카테고리·서비스별 집계)."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM/ADMIN 권한이 필요합니다.")
    if not (1 <= month <= 12):
        raise HTTPException(400, "월은 1~12 사이여야 합니다.")

    items = _filter_costs(year, month)
    total = _sum_costs(items)

    by_category: Dict[str, float] = {}
    by_service:  Dict[str, float] = {}
    for c in items:
        cat = c["category"]
        svc = c["service"]
        by_category[cat] = round(by_category.get(cat, 0) + c["amount"], 2)
        by_service[svc]  = round(by_service.get(svc, 0)  + c["amount"], 2)

    # 전월 비교
    prev_month = month - 1 if month > 1 else 12
    prev_year  = year if month > 1 else year - 1
    prev_items = _filter_costs(prev_year, prev_month)
    prev_total = _sum_costs(prev_items)
    mom_change = round((total - prev_total) / prev_total * 100, 1) if prev_total > 0 else 0.0

    # 예산 대비
    budget_key = f"{year}-{month:02d}"
    budget_rec = _budgets.get(budget_key)
    budget_amt = budget_rec["amount"] if budget_rec else None
    budget_rate = round(total / budget_amt * 100, 1) if budget_amt else None

    return {
        "year":          year,
        "month":         month,
        "period":        f"{year}년 {month}월",
        "total_amount":  total,
        "by_category":   {
            k: {"amount": v, "label": COST_CATEGORIES.get(k, k),
                "pct": round(v / total * 100, 1) if total > 0 else 0}
            for k, v in sorted(by_category.items(), key=lambda x: -x[1])
        },
        "by_service":    {
            k: {"amount": v, "pct": round(v / total * 100, 1) if total > 0 else 0}
            for k, v in sorted(by_service.items(), key=lambda x: -x[1])
        },
        "mom_change_pct": mom_change,
        "budget":        {
            "amount":     budget_amt,
            "usage_pct":  budget_rate,
            "status":     (
                "OVER"   if budget_rate and budget_rate > 110 else
                "WARNING" if budget_rate and budget_rate > 90  else
                "OK"
            ) if budget_rate else "NO_BUDGET",
        },
        "item_count":    len(items),
    }


@router.get("/trend")
async def cost_trend(
    months:  int = Query(6, ge=1, le=24),
    service: Optional[str] = Query(None),
    current_user: User = Depends(get_current_user),
    _db:     AsyncSession = Depends(get_db),
):
    """최근 N개월 비용 트렌드."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM/ADMIN 권한이 필요합니다.")

    now = datetime.utcnow()
    trend = []
    for i in range(months - 1, -1, -1):
        # i개월 전
        target_date = (now.replace(day=1) - timedelta(days=1) * (i * 30))
        y, m = target_date.year, target_date.month
        items = _filter_costs(y, m, service)
        total = _sum_costs(items)
        trend.append({
            "year":         y,
            "month":        m,
            "period":       f"{y}-{m:02d}",
            "total_amount": total,
            "item_count":   len(items),
        })

    # 추세 계산 (첫 달 대비 마지막 달)
    first_amt = trend[0]["total_amount"]  if trend else 0
    last_amt  = trend[-1]["total_amount"] if trend else 0
    trend_pct = round((last_amt - first_amt) / first_amt * 100, 1) if first_amt > 0 else 0.0

    return {
        "months":     months,
        "service":    service or "ALL",
        "data":       trend,
        "trend_pct":  trend_pct,
        "trend_dir":  "UP" if trend_pct > 5 else "DOWN" if trend_pct < -5 else "STABLE",
    }


@router.get("/allocation")
async def cost_allocation(
    year:  int = Query(...),
    month: int = Query(...),
    current_user: User = Depends(get_current_user),
    _db:   AsyncSession = Depends(get_db),
):
    """서비스별 비용 배분 (Cost Allocation)."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM/ADMIN 권한이 필요합니다.")

    items = _filter_costs(year, month)
    total = _sum_costs(items)

    allocation: Dict[str, Dict] = {}
    for svc in SERVICES:
        svc_items  = [c for c in items if c["service"] == svc]
        svc_total  = _sum_costs(svc_items)
        svc_pct    = round(svc_total / total * 100, 1) if total > 0 else 0.0
        by_cat     = {}
        for c in svc_items:
            cat = c["category"]
            by_cat[cat] = round(by_cat.get(cat, 0) + c["amount"], 2)
        allocation[svc] = {
            "amount":      svc_total,
            "pct":         svc_pct,
            "item_count":  len(svc_items),
            "by_category": by_cat,
        }

    return {
        "year":       year,
        "month":      month,
        "total":      total,
        "allocation": allocation,
    }


@router.get("/anomalies")
async def cost_anomalies(
    year:      int   = Query(...),
    month:     int   = Query(...),
    threshold: float = Query(30.0, ge=5.0, le=200.0),  # % 초과시 이상
    current_user: User = Depends(get_current_user),
    _db:       AsyncSession = Depends(get_db),
):
    """비용 이상 탐지 (전월 대비 임계치 초과 서비스 감지)."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM/ADMIN 권한이 필요합니다.")

    prev_month = month - 1 if month > 1 else 12
    prev_year  = year if month > 1 else year - 1

    anomalies = []
    for svc in SERVICES:
        curr_items = _filter_costs(year,      month,      svc)
        prev_items = _filter_costs(prev_year, prev_month, svc)
        curr_amt   = _sum_costs(curr_items)
        prev_amt   = _sum_costs(prev_items)

        if prev_amt <= 0:
            continue
        change_pct = round((curr_amt - prev_amt) / prev_amt * 100, 1)
        if abs(change_pct) >= threshold:
            anomalies.append({
                "service":     svc,
                "curr_amount": curr_amt,
                "prev_amount": prev_amt,
                "change_pct":  change_pct,
                "direction":   "UP" if change_pct > 0 else "DOWN",
                "severity":    "CRITICAL" if abs(change_pct) >= 50 else "WARNING",
            })

    anomalies.sort(key=lambda x: abs(x["change_pct"]), reverse=True)
    return {
        "year":            year,
        "month":           month,
        "threshold_pct":   threshold,
        "anomaly_count":   len(anomalies),
        "anomalies":       anomalies,
    }


@router.get("/recommendations")
async def cost_recommendations(
    year:  int = Query(...),
    month: int = Query(...),
    current_user: User = Depends(get_current_user),
    _db:   AsyncSession = Depends(get_db),
):
    """비용 절감 권고사항 생성."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM/ADMIN 권한이 필요합니다.")

    items = _filter_costs(year, month)
    total = _sum_costs(items)
    recs  = []

    # 단순 규칙 기반 권고
    by_cat: Dict[str, float] = {}
    for c in items:
        cat = c["category"]
        by_cat[cat] = round(by_cat.get(cat, 0) + c["amount"], 2)

    if total > 0:
        license_pct = by_cat.get("LICENSE", 0) / total * 100
        if license_pct > 30:
            recs.append({
                "type":     "LICENSE_REVIEW",
                "severity": "HIGH",
                "message":  f"소프트웨어 라이선스 비용이 전체의 {license_pct:.1f}%입니다. "
                            "미사용 라이선스 해지를 검토하세요.",
                "potential_saving_pct": 10,
            })

        maintenance_pct = by_cat.get("MAINTENANCE", 0) / total * 100
        if maintenance_pct > 25:
            recs.append({
                "type":     "MAINTENANCE_REVIEW",
                "severity": "MEDIUM",
                "message":  f"유지보수 비용({maintenance_pct:.1f}%)이 높습니다. "
                            "계약 재협상 또는 자체 유지보수 전환을 검토하세요.",
                "potential_saving_pct": 5,
            })

    # 전월 대비 급증 서비스 경고
    prev_month = month - 1 if month > 1 else 12
    prev_year  = year if month > 1 else year - 1
    for svc in SERVICES:
        curr = _sum_costs(_filter_costs(year,      month,      svc))
        prev = _sum_costs(_filter_costs(prev_year, prev_month, svc))
        if prev > 0 and (curr - prev) / prev > 0.5:
            recs.append({
                "type":     "COST_SPIKE",
                "severity": "HIGH",
                "message":  f"{svc} 서비스 비용이 전월 대비 "
                            f"{(curr-prev)/prev*100:.0f}% 급증했습니다. 원인 조사가 필요합니다.",
                "potential_saving_pct": 15,
            })

    if not recs:
        recs.append({
            "type":     "NORMAL",
            "severity": "INFO",
            "message":  "현재 비용 구조가 정상 범위입니다.",
            "potential_saving_pct": 0,
        })

    total_potential = sum(r.get("potential_saving_pct", 0) for r in recs if r["type"] != "NORMAL")
    return {
        "year":                  year,
        "month":                 month,
        "total_amount":          total,
        "recommendation_count":  len(recs),
        "recommendations":       recs,
        "total_potential_saving_pct": total_potential,
    }


@router.post("/budget", status_code=201)
async def set_budget(
    body:         BudgetIn,
    current_user: User = Depends(get_current_user),
    _db:          AsyncSession = Depends(get_db),
):
    """예산 등록 또는 수정 (ADMIN)."""
    if current_user.role != UserRole.ADMIN:
        raise HTTPException(403, "ADMIN 권한이 필요합니다.")
    if not (1 <= body.month <= 12):
        raise HTTPException(400, "월은 1~12 사이여야 합니다.")
    if body.amount < 0:
        raise HTTPException(400, "예산은 0 이상이어야 합니다.")

    key = f"{body.year}-{body.month:02d}"
    _budgets[key] = {
        "year":       body.year,
        "month":      body.month,
        "service":    body.service,
        "amount":     body.amount,
        "set_by":     current_user.username,
        "set_at":     datetime.utcnow().isoformat(),
    }
    logger.info("예산 설정: %s %s %.0f원", key, body.service, body.amount)
    return {"message": f"{body.year}년 {body.month}월 예산이 설정되었습니다.",
            "budget": _budgets[key]}


@router.get("/budget")
async def get_budget(
    year:  int = Query(...),
    month: int = Query(...),
    current_user: User = Depends(get_current_user),
    _db:   AsyncSession = Depends(get_db),
):
    """예산 대비 실적 조회."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM/ADMIN 권한이 필요합니다.")

    key        = f"{year}-{month:02d}"
    budget_rec = _budgets.get(key)
    items      = _filter_costs(year, month)
    actual     = _sum_costs(items)

    if not budget_rec:
        return {
            "year": year, "month": month,
            "budget": None,
            "actual": actual,
            "status": "NO_BUDGET",
            "message": "해당 월 예산이 설정되지 않았습니다.",
        }

    budget_amt = budget_rec["amount"]
    variance   = round(actual - budget_amt, 2)
    usage_pct  = round(actual / budget_amt * 100, 1) if budget_amt > 0 else 0.0
    status     = (
        "OVER"    if usage_pct > 110 else
        "WARNING" if usage_pct > 90  else
        "OK"
    )

    return {
        "year":         year,
        "month":        month,
        "budget":       budget_amt,
        "actual":       actual,
        "variance":     variance,
        "usage_pct":    usage_pct,
        "status":       status,
        "remaining":    round(budget_amt - actual, 2),
        "item_count":   len(items),
    }


@router.get("/optimize")
async def ai_optimize(
    year:  int = Query(...),
    month: int = Query(...),
    current_user: User = Depends(get_current_user),
    _db:   AsyncSession = Depends(get_db),
):
    """Ollama sLLM 기반 비용 최적화 분석."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM/ADMIN 권한이 필요합니다.")

    items      = _filter_costs(year, month)
    total      = _sum_costs(items)
    by_cat: Dict[str, float] = {}
    for c in items:
        cat = c["category"]
        by_cat[cat] = round(by_cat.get(cat, 0) + c["amount"], 2)

    # Ollama sLLM 최적화 분석 (내부 LLM only)
    llm_analysis: Optional[str] = None
    try:
        import httpx
        prompt = (
            "다음 IT 인프라 비용 현황을 분석하여 비용 절감 방안을 한국어로 3-5가지 제시하세요.\n\n"
            f"월: {year}년 {month}월\n"
            f"총 비용: {total:,.0f}원\n"
            + "\n".join(f"{COST_CATEGORIES.get(k, k)}: {v:,.0f}원" for k, v in by_cat.items())
        )
        async with httpx.AsyncClient(timeout=15.0) as client:
            resp = await client.post(
                "http://localhost:11434/api/generate",
                json={"model": "llama3", "prompt": prompt, "stream": False},
            )
            if resp.status_code == 200:
                llm_analysis = resp.json().get("response", "").strip()
    except Exception:
        pass

    # 폴백: 규칙 기반 분석
    if not llm_analysis:
        tips = [
            "유휴 서버 자원을 정기적으로 점검하고 불필요한 서버는 폐기하세요.",
            "소프트웨어 라이선스 사용 현황을 분기마다 감사하여 미사용 라이선스를 해지하세요.",
            "유지보수 계약을 통합·재협상하여 규모의 경제를 활용하세요.",
            "네트워크 대역폭 사용 패턴을 분석하여 과잉 회선을 축소하세요.",
            "스토리지 티어링(hot/warm/cold)을 적용하여 보관 비용을 절감하세요.",
        ]
        llm_analysis = "\n".join(f"{i+1}. {t}" for i, t in enumerate(tips))

    return {
        "year":            year,
        "month":           month,
        "total_amount":    total,
        "by_category":     by_cat,
        "optimization":    llm_analysis,
        "llm_used":        "ollama/llama3",
        "disclaimer":      "AI 분석은 참고용입니다. 실제 절감 가능 여부는 현장 검토 후 판단하세요.",
    }
