"""
CMDB: Institution + Server 관리 + C-1 CI 형상 관리 확장

엔드포인트:
  기존: /api/cmdb/institutions, /api/cmdb/servers
  C-1:
    POST   /api/cmdb/ci               — CI 생성
    GET    /api/cmdb/ci               — CI 목록
    GET    /api/cmdb/ci/{ci_id}       — CI 상세 + 관계 + 변경 이력
    PATCH  /api/cmdb/ci/{ci_id}       — CI 수정
    DELETE /api/cmdb/ci/{ci_id}       — CI 폐기(RETIRED 처리)
    POST   /api/cmdb/ci/relations     — 관계 추가
    DELETE /api/cmdb/ci/relations/{id} — 관계 삭제
    GET    /api/cmdb/ci/{ci_id}/history — 변경 이력
    GET    /api/cmdb/ci/stats         — CI 통계
    POST   /api/cmdb/ci/import-servers — 기존 서버 → CI 일괄 등록
"""
from datetime import datetime, date
from typing import Dict, List, Optional

import json

from fastapi import APIRouter, 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 middleware.license_guard import check_server_limit
from models import (
    Institution, InstitutionOut,
    Server, ServerOut, ServerUpdate,
    User, UserRole,
    ConfigItem, ConfigItemOut, ConfigItemCreate, ConfigItemUpdate,
    CIRelation, CIRelationOut, CIRelationCreate,
    CIChangeLog, CIChangeLogOut,
    CIChangeType,
)

router = APIRouter(prefix="/api/cmdb", tags=["cmdb"])


@router.get("/institutions", response_model=List[InstitutionOut])
async def list_institutions(db: AsyncSession = Depends(get_db),
                             _u: User = Depends(get_current_user)):
    result = await db.execute(select(Institution).order_by(Institution.inst_name))
    return result.scalars().all()


@router.get("/institutions/{inst_code}/servers", response_model=List[ServerOut])
async def list_servers(inst_code: str, db: AsyncSession = Depends(get_db),
                       _u: User = Depends(get_current_user)):
    r = await db.execute(
        select(Institution).where(Institution.inst_code == inst_code)
    )
    inst = r.scalars().first()
    if not inst:
        raise HTTPException(404, detail="기관을 찾을 수 없습니다.")

    result = await db.execute(
        select(Server).where(Server.inst_id == inst.id, Server.is_active == True)
                       .order_by(Server.server_role, Server.server_name)
    )
    return result.scalars().all()


@router.get("/servers", response_model=List[ServerOut])
async def list_all_servers(
    role: Optional[str] = Query(None),
    inst_code: Optional[str] = Query(None),
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    q = select(Server).where(Server.is_active == True)
    if role:
        q = q.where(Server.server_role == role)
    if inst_code:
        r = await db.execute(select(Institution).where(Institution.inst_code == inst_code))
        inst = r.scalars().first()
        if inst:
            q = q.where(Server.inst_id == inst.id)
    q = q.order_by(Server.server_role, Server.server_name)
    result = await db.execute(q)
    return result.scalars().all()


@router.get("/servers/{server_id}", response_model=ServerOut)
async def get_server(
    server_id: int,
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    r = await db.execute(select(Server).where(Server.id == server_id))
    server = r.scalars().first()
    if not server:
        raise HTTPException(404, "서버를 찾을 수 없습니다.")
    return server


@router.patch("/servers/{server_id}", response_model=ServerOut)
async def update_server(
    server_id: int,
    payload: ServerUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    if current_user.role not in (UserRole.ADMIN, UserRole.PM, UserRole.ENGINEER):
        raise HTTPException(403, "권한이 없습니다.")
    r = await db.execute(select(Server).where(Server.id == server_id))
    server = r.scalars().first()
    if not server:
        raise HTTPException(404, "서버를 찾을 수 없습니다.")
    for k, v in payload.model_dump(exclude_unset=True).items():
        setattr(server, k, v)
    server.updated_at = datetime.now()
    await db.commit()
    await db.refresh(server)
    return server


@router.post("/servers", response_model=ServerOut, status_code=201)
async def create_server(
    payload: dict,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
    _: None = Depends(check_server_limit),
):
    """새 서버 자산 등록."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "ADMIN 또는 PM 권한이 필요합니다.")
    # inst_code → inst_id 변환
    inst_code = payload.pop("inst_code", None)
    inst_id = None
    if inst_code:
        r = await db.execute(
            select(Institution).where(Institution.inst_code == inst_code)
        )
        inst = r.scalars().first()
        if inst:
            inst_id = inst.id
    # sensitive fields: if ip_addr/ssh_user/os_pw_enc provided, store them (encryption handled elsewhere)
    server = Server(inst_id=inst_id, **{k: v for k, v in payload.items()
                                         if hasattr(Server, k)})
    db.add(server)
    await db.commit()
    await db.refresh(server)
    return server


# ════════════════════════════════════════════════════════════════════════════════
# C-1: CI 형상 관리 (Configuration Item Management)
# ════════════════════════════════════════════════════════════════════════════════

async def _next_ci_id(db: AsyncSession) -> str:
    """CI-YYYYMMDD-NNNN 형식 CI ID 생성."""
    today  = datetime.utcnow().strftime("%Y%m%d")
    prefix = f"CI-{today}-"
    last   = (await db.execute(
        select(ConfigItem.ci_id)
        .where(ConfigItem.ci_id.like(f"{prefix}%"))
        .order_by(desc(ConfigItem.ci_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 _log_ci_change(
    db: AsyncSession,
    ci: ConfigItem,
    change_type: str,
    changed_by: str,
    field_name: Optional[str] = None,
    old_value: Optional[str] = None,
    new_value: Optional[str] = None,
    sr_id: Optional[str] = None,
    note: Optional[str] = None,
):
    """CI 변경 이력 기록."""
    log = CIChangeLog(
        ci_id_fk    = ci.id,
        ci_id_str   = ci.ci_id,
        change_type = change_type,
        field_name  = field_name,
        old_value   = old_value,
        new_value   = new_value,
        changed_by  = changed_by,
        sr_id       = sr_id,
        note        = note,
    )
    db.add(log)


# ── CI CRUD ───────────────────────────────────────────────────────────────────

@router.post("/ci", response_model=ConfigItemOut, status_code=201)
async def create_ci(
    body: ConfigItemCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """새 형상 항목(CI) 등록."""
    ci_id = await _next_ci_id(db)
    ci = ConfigItem(
        ci_id         = ci_id,
        name          = body.name,
        ci_type       = body.ci_type.upper(),
        category      = body.category,
        status        = body.status.upper(),
        version       = body.version,
        owner         = body.owner,
        location      = body.location,
        vendor        = body.vendor,
        model         = body.model,
        serial_number = body.serial_number,
        install_date  = body.install_date,
        retire_date   = body.retire_date,
        linked_server_id = body.linked_server_id,
        inst_id       = body.inst_id,
        sr_id         = body.sr_id,
        attributes_json = json.dumps(body.attributes, ensure_ascii=False) if body.attributes else None,
        description   = body.description,
        created_by    = current_user.username,
    )
    db.add(ci)
    await db.flush()
    await _log_ci_change(
        db, ci,
        change_type = CIChangeType.CREATE.value,
        changed_by  = current_user.username,
        new_value   = body.name,
        note        = "CI 신규 등록",
    )
    await db.commit()
    await db.refresh(ci)
    return ci


@router.get("/ci", response_model=List[ConfigItemOut])
async def list_ci(
    ci_type:  Optional[str] = Query(None),
    status:   Optional[str] = Query(None),
    inst_id:  Optional[int] = Query(None),
    keyword:  Optional[str] = Query(None),
    limit:    int = Query(50, ge=1, le=200),
    offset:   int = Query(0, ge=0),
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """CI 목록 조회."""
    conditions = []
    if ci_type:
        conditions.append(ConfigItem.ci_type == ci_type.upper())
    if status:
        conditions.append(ConfigItem.status == status.upper())
    if inst_id:
        conditions.append(ConfigItem.inst_id == inst_id)
    if keyword:
        conditions.append(ConfigItem.name.ilike(f"%{keyword}%"))

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


@router.get("/ci/stats")
async def ci_stats(
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """CI 현황 통계."""
    total = (await db.execute(
        select(func.count()).select_from(ConfigItem)
    )).scalar() or 0

    by_type = {}
    type_rows = (await db.execute(
        select(ConfigItem.ci_type, func.count())
        .group_by(ConfigItem.ci_type)
    )).all()
    for ci_type, cnt in type_rows:
        by_type[ci_type] = cnt

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

    relation_count = (await db.execute(
        select(func.count()).select_from(CIRelation)
    )).scalar() or 0

    change_count = (await db.execute(
        select(func.count()).select_from(CIChangeLog)
    )).scalar() or 0

    return {
        "total_ci":         total,
        "by_type":          by_type,
        "by_status":        by_status,
        "total_relations":  relation_count,
        "total_changes":    change_count,
    }


@router.get("/ci/{ci_id}", response_model=ConfigItemOut)
async def get_ci(
    ci_id: str,
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """CI 상세 조회."""
    ci = (await db.execute(
        select(ConfigItem).where(ConfigItem.ci_id == ci_id)
    )).scalars().first()
    if not ci:
        raise HTTPException(404, f"CI {ci_id}를 찾을 수 없습니다.")
    return ci


@router.patch("/ci/{ci_id}", response_model=ConfigItemOut)
async def update_ci(
    ci_id: str,
    body: ConfigItemUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """CI 수정 — 변경된 필드마다 이력 기록."""
    ci = (await db.execute(
        select(ConfigItem).where(ConfigItem.ci_id == ci_id)
    )).scalars().first()
    if not ci:
        raise HTTPException(404, f"CI {ci_id}를 찾을 수 없습니다.")

    update_data = body.model_dump(exclude_unset=True, exclude_none=True)
    sr_id_for_log = update_data.pop("sr_id", None)

    for field, new_val in update_data.items():
        if field == "attributes":
            old_v = ci.attributes_json
            ci.attributes_json = json.dumps(new_val, ensure_ascii=False) if new_val else None
            new_v = ci.attributes_json
        else:
            old_v = str(getattr(ci, field, "")) if getattr(ci, field, None) is not None else None
            setattr(ci, field, new_val)
            new_v = str(new_val)

        # 상태 변경은 별도 change_type 사용
        ct = CIChangeType.STATUS_CHANGE.value if field == "status" else CIChangeType.UPDATE.value
        await _log_ci_change(
            db, ci, ct,
            changed_by  = current_user.username,
            field_name  = field,
            old_value   = old_v,
            new_value   = new_v,
            sr_id       = sr_id_for_log,
        )

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


@router.delete("/ci/{ci_id}", status_code=204)
async def retire_ci(
    ci_id: str,
    note: Optional[str] = Query(None, description="폐기 사유"),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """CI 폐기 처리 (RETIRED 상태 전환 — 데이터 보존)."""
    ci = (await db.execute(
        select(ConfigItem).where(ConfigItem.ci_id == ci_id)
    )).scalars().first()
    if not ci:
        raise HTTPException(404, f"CI {ci_id}를 찾을 수 없습니다.")
    if ci.status == "RETIRED":
        raise HTTPException(400, "이미 폐기된 CI입니다.")

    old_status = ci.status
    ci.status  = "RETIRED"
    ci.retire_date = datetime.utcnow().date()
    await _log_ci_change(
        db, ci,
        change_type = CIChangeType.RETIRE.value,
        changed_by  = current_user.username,
        field_name  = "status",
        old_value   = old_status,
        new_value   = "RETIRED",
        note        = note or "폐기 처리",
    )
    await db.commit()


# ── CI 관계 관리 ─────────────────────────────────────────────────────────────

@router.post("/ci/relations", response_model=CIRelationOut, status_code=201)
async def add_ci_relation(
    body: CIRelationCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """CI 간 관계 추가."""
    from_ci = (await db.execute(select(ConfigItem).where(ConfigItem.id == body.from_ci_id))).scalars().first()
    to_ci   = (await db.execute(select(ConfigItem).where(ConfigItem.id == body.to_ci_id))).scalars().first()
    if not from_ci:
        raise HTTPException(404, f"출발 CI id={body.from_ci_id} 없음")
    if not to_ci:
        raise HTTPException(404, f"도착 CI id={body.to_ci_id} 없음")

    # 중복 검사
    existing = (await db.execute(
        select(CIRelation).where(
            and_(
                CIRelation.from_ci_id == body.from_ci_id,
                CIRelation.to_ci_id   == body.to_ci_id,
                CIRelation.relation_type == body.relation_type.upper(),
            )
        )
    )).scalars().first()
    if existing:
        raise HTTPException(409, "동일 관계가 이미 존재합니다.")

    relation = CIRelation(
        from_ci_id    = body.from_ci_id,
        to_ci_id      = body.to_ci_id,
        relation_type = body.relation_type.upper(),
        description   = body.description,
        created_by    = current_user.username,
    )
    db.add(relation)
    await db.flush()

    # 양쪽 CI에 이력 기록
    for ci, note in [
        (from_ci, f"{body.relation_type} → CI#{body.to_ci_id}({to_ci.name}) 관계 추가"),
        (to_ci,   f"CI#{body.from_ci_id}({from_ci.name}) ← {body.relation_type} 관계 추가"),
    ]:
        await _log_ci_change(
            db, ci,
            change_type = CIChangeType.RELATION_ADD.value,
            changed_by  = current_user.username,
            note        = note,
        )

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


@router.delete("/ci/relations/{relation_id}", status_code=204)
async def remove_ci_relation(
    relation_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """CI 관계 삭제."""
    rel = (await db.execute(select(CIRelation).where(CIRelation.id == relation_id))).scalars().first()
    if not rel:
        raise HTTPException(404, "관계를 찾을 수 없습니다.")

    from_ci = (await db.execute(select(ConfigItem).where(ConfigItem.id == rel.from_ci_id))).scalars().first()
    to_ci   = (await db.execute(select(ConfigItem).where(ConfigItem.id == rel.to_ci_id))).scalars().first()

    for ci in [c for c in [from_ci, to_ci] if c]:
        await _log_ci_change(
            db, ci,
            change_type = CIChangeType.RELATION_DEL.value,
            changed_by  = current_user.username,
            note        = f"관계(id={relation_id}, type={rel.relation_type}) 삭제",
        )

    await db.delete(rel)
    await db.commit()


@router.get("/ci/{ci_id}/relations")
async def get_ci_relations(
    ci_id: str,
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """CI의 모든 관계 조회 (출발+도착 모두)."""
    ci = (await db.execute(select(ConfigItem).where(ConfigItem.ci_id == ci_id))).scalars().first()
    if not ci:
        raise HTTPException(404, f"CI {ci_id} 없음")

    # 출발 관계
    out_rels = (await db.execute(
        select(CIRelation).where(CIRelation.from_ci_id == ci.id)
    )).scalars().all()

    # 도착 관계
    in_rels = (await db.execute(
        select(CIRelation).where(CIRelation.to_ci_id == ci.id)
    )).scalars().all()

    # 관련 CI 이름 보강
    async def enrich(rel: CIRelation, direction: str):
        other_id  = rel.to_ci_id if direction == "outbound" else rel.from_ci_id
        other_ci  = (await db.execute(select(ConfigItem).where(ConfigItem.id == other_id))).scalars().first()
        return {
            "id":           rel.id,
            "direction":    direction,
            "relation_type": rel.relation_type,
            "from_ci_id":   rel.from_ci_id,
            "to_ci_id":     rel.to_ci_id,
            "other_ci_id":  other_ci.ci_id if other_ci else None,
            "other_ci_name": other_ci.name if other_ci else None,
            "description":  rel.description,
            "created_at":   rel.created_at.isoformat(),
        }

    result = []
    for r in out_rels:
        result.append(await enrich(r, "outbound"))
    for r in in_rels:
        result.append(await enrich(r, "inbound"))

    return {"ci_id": ci_id, "relations": result}


# ── CI 변경 이력 ─────────────────────────────────────────────────────────────

@router.get("/ci/{ci_id}/history", response_model=List[CIChangeLogOut])
async def get_ci_history(
    ci_id: str,
    limit: int = Query(50, ge=1, le=200),
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """CI 변경 이력 조회."""
    ci = (await db.execute(select(ConfigItem).where(ConfigItem.ci_id == ci_id))).scalars().first()
    if not ci:
        raise HTTPException(404, f"CI {ci_id} 없음")

    logs = (await db.execute(
        select(CIChangeLog)
        .where(CIChangeLog.ci_id_fk == ci.id)
        .order_by(desc(CIChangeLog.changed_at))
        .limit(limit)
    )).scalars().all()
    return logs


# ── 서버 → CI 일괄 등록 ───────────────────────────────────────────────────────

@router.post("/ci/import-servers")
async def import_servers_as_ci(
    inst_id: Optional[int] = Query(None, description="특정 기관만 가져오기"),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    기존 등록된 서버를 CI로 일괄 등록 (이미 CI가 있는 서버는 스킵).
    """
    if current_user.role not in (UserRole.ADMIN, UserRole.PM, UserRole.ENGINEER):
        raise HTTPException(403, "권한 부족")

    q = select(Server).where(Server.is_active == True)
    if inst_id:
        q = q.where(Server.inst_id == inst_id)
    servers = (await db.execute(q)).scalars().all()

    created, skipped = 0, 0
    for srv in servers:
        # 이미 linked_server_id로 CI가 있으면 스킵
        existing = (await db.execute(
            select(ConfigItem).where(ConfigItem.linked_server_id == srv.id)
        )).scalars().first()
        if existing:
            skipped += 1
            continue

        ci_id = await _next_ci_id(db)
        # server_role → ci_type 매핑
        type_map = {
            "WEB": "SERVER", "WAS": "SERVER", "DB": "DATABASE",
            "ESB": "MIDDLEWARE", "BATCH": "SERVER",
        }
        srv_role = getattr(srv, "server_role", "") or ""
        ci_type  = type_map.get(srv_role.upper(), "SERVER")

        ci = ConfigItem(
            ci_id           = ci_id,
            name            = srv.server_name,
            ci_type         = ci_type,
            category        = srv_role or "SERVER",
            status          = "ACTIVE" if srv.is_active else "INACTIVE",
            owner           = srv.maintenance_contact,
            install_date    = srv.install_date,
            retire_date     = srv.eol_date,
            linked_server_id = srv.id,
            inst_id         = srv.inst_id,
            description     = f"서버 자동 등록: {srv.server_name} ({srv.os_type or ''})",
            created_by      = current_user.username,
        )
        db.add(ci)
        await db.flush()
        await _log_ci_change(
            db, ci,
            change_type = CIChangeType.CREATE.value,
            changed_by  = current_user.username,
            note        = f"서버 일괄 등록 (server_id={srv.id})",
        )
        created += 1

    await db.commit()
    return {
        "created":  created,
        "skipped":  skipped,
        "total_servers": len(servers),
    }
