"""
D-1: LDAP/AD 연동 API 라우터

엔드포인트:
  GET  /api/ldap/status          — LDAP 연결 상태 조회
  POST /api/ldap/test            — LDAP 연결 테스트 (ping)
  POST /api/ldap/authenticate    — LDAP 인증 테스트 (관리자용)
  GET  /api/ldap/config          — 현재 LDAP 설정 조회 (민감정보 마스킹)
  PUT  /api/ldap/config          — LDAP 설정 업데이트
  GET  /api/ldap/group-map       — 그룹→역할 매핑 조회
  PUT  /api/ldap/group-map       — 그룹→역할 매핑 업데이트
  POST /api/ldap/sync/{username} — 특정 사용자 LDAP 동기화
  GET  /api/ldap/users           — LDAP 연동 사용자 목록
"""
from __future__ import annotations

import logging
import os
from typing import Dict, Optional

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy import select
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/ldap", tags=["ldap"])


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

class LDAPConfigIn(BaseModel):
    """LDAP 설정 업데이트 요청."""
    server_url:       Optional[str]  = None
    base_dn:          Optional[str]  = None
    bind_dn:          Optional[str]  = None
    bind_password:    Optional[str]  = None   # 입력만 허용, 출력 절대 불가
    user_search_base: Optional[str]  = None
    group_search_base: Optional[str] = None
    user_filter:      Optional[str]  = None
    use_ssl:          Optional[bool] = None
    use_tls:          Optional[bool] = None
    timeout:          Optional[int]  = None
    enabled:          Optional[bool] = None


class LDAPAuthTestIn(BaseModel):
    """LDAP 인증 테스트 요청."""
    username: str
    password: str


class GroupRoleMapIn(BaseModel):
    """그룹→역할 매핑 업데이트."""
    mapping: Dict[str, str]   # {"GroupName": "ADMIN|PM|ENGINEER|VIEWER"}


# ── 임시 설정 저장소 (실운영에서는 암호화 DB 컬럼 사용) ────────────────────────────
_runtime_override: Dict = {}


def _get_masked_config() -> Dict:
    """LDAP 설정 반환 (bind_password 마스킹)."""
    from core.ldap_auth import get_ldap_config
    cfg = get_ldap_config()
    return {
        "server_url":        cfg.server_url,
        "base_dn":           cfg.base_dn,
        "bind_dn":           cfg.bind_dn,
        "bind_password":     "***" if cfg.bind_password else "",   # 절대 노출 금지
        "user_search_base":  cfg.user_search_base,
        "group_search_base": cfg.group_search_base,
        "user_filter":       cfg.user_filter,
        "group_attr":        cfg.group_attr,
        "uid_attr":          cfg.uid_attr,
        "email_attr":        cfg.email_attr,
        "display_name_attr": cfg.display_name_attr,
        "department_attr":   cfg.department_attr,
        "use_ssl":           cfg.use_ssl,
        "use_tls":           cfg.use_tls,
        "timeout":           cfg.timeout,
        "enabled":           cfg.enabled,
    }


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

@router.get("/status")
async def ldap_status(
    _u: User = Depends(get_current_user),
):
    """LDAP 연결 상태 및 설정 요약."""
    from core.ldap_auth import get_ldap_config
    cfg = get_ldap_config()
    return {
        "enabled":    cfg.enabled,
        "server_url": cfg.server_url if cfg.server_url else "(미설정)",
        "base_dn":    cfg.base_dn    if cfg.base_dn    else "(미설정)",
        "bind_dn":    cfg.bind_dn    if cfg.bind_dn    else "(미설정)",
        "use_ssl":    cfg.use_ssl,
        "use_tls":    cfg.use_tls,
        "configured": bool(cfg.server_url and cfg.bind_dn),
    }


@router.post("/test")
async def test_connection(
    current_user: User = Depends(get_current_user),
):
    """LDAP 서비스 계정으로 연결 테스트."""
    if current_user.role not in (UserRole.ADMIN,):
        raise HTTPException(403, "ADMIN 권한이 필요합니다.")

    from core.ldap_auth import test_ldap_connection
    result = test_ldap_connection()
    # bind_dn만 노출, bind_password 절대 미포함
    result.pop("bind_password", None)
    return result


@router.post("/authenticate")
async def test_authenticate(
    body: LDAPAuthTestIn,
    current_user: User = Depends(get_current_user),
):
    """LDAP 사용자 인증 테스트 (관리자용)."""
    if current_user.role not in (UserRole.ADMIN,):
        raise HTTPException(403, "ADMIN 권한이 필요합니다.")

    from core.ldap_auth import authenticate_ldap
    success, user_info, error = authenticate_ldap(body.username, body.password)

    if not success:
        return {"success": False, "error": error}

    # 민감정보 제외하고 반환
    safe_info = {k: v for k, v in (user_info or {}).items() if k != "dn"}
    return {"success": True, "user_info": safe_info}


@router.get("/config")
async def get_config(
    current_user: User = Depends(get_current_user),
):
    """현재 LDAP 설정 조회 (비밀번호 마스킹)."""
    if current_user.role not in (UserRole.ADMIN,):
        raise HTTPException(403, "ADMIN 권한이 필요합니다.")
    return _get_masked_config()


@router.put("/config")
async def update_config(
    body: LDAPConfigIn,
    current_user: User = Depends(get_current_user),
):
    """LDAP 설정 업데이트 (환경변수 재적용)."""
    if current_user.role not in (UserRole.ADMIN,):
        raise HTTPException(403, "ADMIN 권한이 필요합니다.")

    from core.ldap_auth import get_ldap_config, init_ldap_config, LDAPConfig
    import dataclasses

    cfg = get_ldap_config()
    cfg_dict = dataclasses.asdict(cfg)

    # 요청값으로 덮어쓰기 (비밀번호는 제공된 경우만)
    update_data = body.model_dump(exclude_none=True)
    cfg_dict.update(update_data)

    new_cfg = LDAPConfig(**cfg_dict)
    init_ldap_config(new_cfg)

    logger.info("LDAP 설정 업데이트 by %s", current_user.username)
    # 비밀번호 마스킹 후 반환
    result = _get_masked_config()
    return {"message": "LDAP 설정이 업데이트되었습니다.", "config": result}


@router.get("/group-map")
async def get_group_map(
    current_user: User = Depends(get_current_user),
):
    """그룹→역할 매핑 조회."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM/ADMIN 권한이 필요합니다.")

    from core.ldap_auth import _group_role_map, DEFAULT_GROUP_ROLE_MAP
    return {
        "current_mapping": dict(_group_role_map),
        "default_mapping": DEFAULT_GROUP_ROLE_MAP,
    }


@router.put("/group-map")
async def update_group_map(
    body: GroupRoleMapIn,
    current_user: User = Depends(get_current_user),
):
    """그룹→역할 매핑 업데이트."""
    if current_user.role not in (UserRole.ADMIN,):
        raise HTTPException(403, "ADMIN 권한이 필요합니다.")

    valid_roles = {"ADMIN", "PM", "ENGINEER", "VIEWER"}
    for grp, role in body.mapping.items():
        if role.upper() not in valid_roles:
            raise HTTPException(400, f"유효하지 않은 역할: {role}. 허용값: {valid_roles}")

    from core.ldap_auth import set_group_role_map
    normalized = {k: v.upper() for k, v in body.mapping.items()}
    set_group_role_map(normalized)

    logger.info("그룹→역할 매핑 업데이트 by %s: %d 개", current_user.username, len(normalized))
    return {"message": f"{len(normalized)}개 그룹 매핑이 업데이트되었습니다.", "mapping": normalized}


@router.post("/sync/{username}")
async def sync_user(
    username: str,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """특정 사용자를 LDAP에서 조회하여 로컬 DB와 동기화."""
    if current_user.role not in (UserRole.ADMIN,):
        raise HTTPException(403, "ADMIN 권한이 필요합니다.")

    from core.ldap_auth import get_ldap_config, authenticate_ldap
    cfg = get_ldap_config()

    if not cfg.enabled:
        raise HTTPException(503, "LDAP가 비활성화 상태입니다.")

    # 서비스 계정으로 사용자 정보만 조회 (인증 없이)
    # authenticate_ldap은 인증도 포함하므로 여기서는 직접 조회 로직 구현
    ldap3 = None
    try:
        import ldap3 as _ldap3  # type: ignore
        ldap3 = _ldap3
    except ImportError:
        raise HTTPException(503, "ldap3 패키지가 설치되지 않았습니다.")

    try:
        server = ldap3.Server(cfg.server_url, use_ssl=cfg.use_ssl, connect_timeout=cfg.timeout)
        with ldap3.Connection(server, user=cfg.bind_dn, password=cfg.bind_password,
                               auto_bind=ldap3.AUTO_BIND_NO_TLS) as conn:
            if cfg.use_tls:
                conn.start_tls()
            user_filter = cfg.user_filter.format(username=username)
            attrs = [cfg.uid_attr, cfg.email_attr, cfg.display_name_attr,
                     cfg.department_attr, cfg.group_attr]
            conn.search(
                search_base=cfg.user_search_base or cfg.base_dn,
                search_filter=user_filter,
                attributes=attrs,
            )
            if not conn.entries:
                raise HTTPException(404, f"LDAP에서 사용자 '{username}'를 찾을 수 없습니다.")

            entry = conn.entries[0]
            groups_raw = []
            if hasattr(entry, cfg.group_attr):
                groups_raw = [str(g) for g in getattr(entry, cfg.group_attr)]

            from core.ldap_auth import map_groups_to_role
            groups = []
            for g in groups_raw:
                if g.startswith("CN="):
                    cn = g.split(",")[0].replace("CN=", "")
                    groups.append(cn)
                else:
                    groups.append(g)

            role = map_groups_to_role(groups)
            ldap_info = {
                "username":     str(getattr(entry, cfg.uid_attr, username)),
                "email":        str(getattr(entry, cfg.email_attr, "")) or None,
                "display_name": str(getattr(entry, cfg.display_name_attr, username)),
                "department":   str(getattr(entry, cfg.department_attr, "")) or None,
                "groups":       groups,
                "role":         role,
                "dn":           entry.entry_dn,
            }

        from core.ldap_auth import sync_ldap_user
        user = await sync_ldap_user(db, username, ldap_info)
        if user is None:
            raise HTTPException(500, "사용자 동기화 중 오류가 발생했습니다.")

        return {
            "synced":   True,
            "username": user.username,
            "email":    user.email,
            "role":     user.role.value if hasattr(user.role, "value") else str(user.role),
            "auth_type": getattr(user, "auth_type", "LDAP"),
            "groups":   groups,
        }

    except HTTPException:
        raise
    except Exception as e:
        logger.error("LDAP 사용자 동기화 실패 (%s): %s", username, str(e)[:100])
        raise HTTPException(500, f"LDAP 조회 오류: {str(e)[:80]}")


@router.get("/users")
async def list_ldap_users(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """LDAP 인증 사용자 목록 조회."""
    if current_user.role not in (UserRole.ADMIN, UserRole.PM):
        raise HTTPException(403, "PM/ADMIN 권한이 필요합니다.")

    q = select(User).where(
        User.auth_type == "LDAP"  # type: ignore[attr-defined]
    ).order_by(User.username)

    users = (await db.execute(q)).scalars().all()
    return [
        {
            "username":     u.username,
            "email":        u.email,
            "display_name": getattr(u, "display_name", None),
            "department":   getattr(u, "department",   None),
            "role":         u.role.value if hasattr(u.role, "value") else str(u.role),
            "is_active":    u.is_active,
            "auth_type":    getattr(u, "auth_type", "LDAP"),
        }
        for u in users
    ]
