"""로그인 / 비밀번호 변경 / 내 정보 / MFA (TOTP) 엔드포인트."""
from datetime import datetime, timedelta
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from core.auth import (create_access_token, get_current_user, hash_password,
                       verify_password, MAX_FAILED_ATTEMPTS, LOCKOUT_MINUTES)
from database import get_db
from models import User, UserDevice, LoginEvent, Institution

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


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

class LoginRequest(BaseModel):
    username: str
    password: str


class ChangePasswordRequest(BaseModel):
    current_password: str
    new_password: str


class TokenResponse(BaseModel):
    access_token: str
    token_type: str = "bearer"
    must_change_pw: bool
    username: str
    display_name: str
    role: str
    mfa_enabled: bool = False


class MfaPendingResponse(BaseModel):
    """비밀번호 인증 성공 후 MFA 2단계 필요 시 반환."""
    mfa_required: bool = True
    mfa_token: str     # 5분 유효 임시 토큰


class MfaVerifyRequest(BaseModel):
    """MFA 2단계: mfa_token + TOTP 코드."""
    mfa_token: str
    totp_code: str


class MfaSetupRequest(BaseModel):
    """MFA 등록 활성화 요청."""
    totp_code: str    # 등록 확인용 코드 (앱에서 생성된 첫 코드)


class MfaDisableRequest(BaseModel):
    """MFA 비활성화 요청."""
    password: str     # 본인 확인용 현재 비밀번호
    totp_code: str    # MFA 비활성화를 위한 TOTP 코드 (추가 검증)


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

@router.post("/login")
async def login(body: LoginRequest, db: AsyncSession = Depends(get_db)):
    """
    1단계 로그인 (비밀번호).

    보안 강화:
    - 연속 실패 5회 → 계정 30분 잠금
    - 잠금 상태에서는 올바른 비밀번호를 입력해도 차단
    - 성공 시 실패 횟수 초기화
    """
    result = await db.execute(select(User).where(User.username == body.username))
    user   = result.scalars().first()

    # 계정 존재 여부와 무관하게 동일한 오류 메시지 반환 (사용자 열거 방지)
    _INVALID_MSG = "아이디 또는 비밀번호가 올바르지 않습니다"

    if not user or not user.is_active:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=_INVALID_MSG)

    # 계정 잠금 확인
    now = datetime.now()
    if user.locked_until and user.locked_until > now:
        remaining = int((user.locked_until - now).total_seconds() / 60) + 1
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail=f"계정이 잠겨 있습니다. {remaining}분 후 다시 시도하세요.",
        )

    # 잠금 기간 만료 시 자동 초기화
    if user.locked_until and user.locked_until <= now:
        user.failed_login_count = 0
        user.locked_until       = None

    # 비밀번호 검증
    if not verify_password(body.password, user.hashed_pw):
        user.failed_login_count = (user.failed_login_count or 0) + 1
        if user.failed_login_count >= MAX_FAILED_ATTEMPTS:
            user.locked_until = now + timedelta(minutes=LOCKOUT_MINUTES)
            await db.commit()
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=f"로그인 실패 {MAX_FAILED_ATTEMPTS}회 초과로 계정이 {LOCKOUT_MINUTES}분간 잠겼습니다.",
            )
        await db.commit()
        remaining_attempts = MAX_FAILED_ATTEMPTS - user.failed_login_count
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=f"{_INVALID_MSG} (남은 시도: {remaining_attempts}회)",
        )

    # 로그인 성공 — 실패 횟수 초기화
    user.failed_login_count = 0
    user.locked_until       = None

    # MFA 활성 사용자 → 임시 토큰 반환
    if user.mfa_enabled:
        await db.commit()
        from core.mfa import create_mfa_pending_token
        mfa_token = create_mfa_pending_token(user.username)
        return MfaPendingResponse(mfa_required=True, mfa_token=mfa_token)

    user.last_login_at = datetime.now()
    await db.commit()

    token = create_access_token({"sub": user.username, "role": user.role})
    return TokenResponse(
        access_token=token,
        must_change_pw=user.must_change_pw,
        username=user.username,
        display_name=user.display_name or user.username,
        role=user.role,
        mfa_enabled=False,
    )


@router.post("/login/mfa", response_model=TokenResponse)
async def login_mfa(body: MfaVerifyRequest, db: AsyncSession = Depends(get_db)):
    """
    2단계 MFA 인증 (TOTP).

    mfa_token (POST /api/auth/login 에서 발급된 임시 토큰) +
    totp_code (앱에서 생성된 6자리 코드) 를 검증하여 access_token 발급.
    """
    from core.mfa import verify_mfa_pending_token, decrypt_totp_secret, verify_totp

    username = verify_mfa_pending_token(body.mfa_token)
    if not username:
        raise HTTPException(status_code=401, detail="MFA 토큰이 유효하지 않거나 만료되었습니다.")

    result = await db.execute(select(User).where(User.username == username))
    user = result.scalars().first()
    if not user or not user.is_active or not user.mfa_enabled:
        raise HTTPException(status_code=401, detail="사용자를 찾을 수 없거나 MFA가 비활성화되어 있습니다.")

    # TOTP 코드 검증
    try:
        secret = decrypt_totp_secret(user.totp_secret_enc)
        if not verify_totp(secret, body.totp_code):
            raise HTTPException(status_code=401, detail="TOTP 코드가 올바르지 않습니다.")
    except Exception as e:
        if isinstance(e, HTTPException):
            raise
        raise HTTPException(status_code=500, detail="MFA 검증 중 오류가 발생했습니다.")

    # 로그인 완료
    user.last_login_at = datetime.now()
    await db.commit()

    token = create_access_token({"sub": user.username, "role": user.role})
    return TokenResponse(
        access_token=token,
        must_change_pw=user.must_change_pw,
        username=user.username,
        display_name=user.display_name or user.username,
        role=user.role,
        mfa_enabled=True,
    )


@router.post("/change-password")
async def change_password(
    body: ChangePasswordRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    if not verify_password(body.current_password, current_user.hashed_pw):
        raise HTTPException(status_code=400, detail="현재 비밀번호가 올바르지 않습니다")
    if body.new_password == body.current_password:
        raise HTTPException(status_code=400, detail="새 비밀번호는 현재 비밀번호와 달라야 합니다")
    # 비밀번호 복잡도 검증 (상용화 보안 정책)
    from core.license import check_password_complexity
    ok, msg = check_password_complexity(body.new_password)
    if not ok:
        raise HTTPException(status_code=400, detail=msg)

    # DB 재조회 후 업데이트
    result = await db.execute(select(User).where(User.id == current_user.id))
    user   = result.scalars().first()
    user.hashed_pw      = hash_password(body.new_password)
    user.must_change_pw = False
    await db.commit()
    return {"message": "비밀번호가 변경되었습니다"}


@router.get("/me")
async def me(current_user: User = Depends(get_current_user)):
    return {
        "id":           current_user.id,
        "username":     current_user.username,
        "display_name": current_user.display_name or current_user.username,
        "role":         current_user.role,
        "must_change_pw": current_user.must_change_pw,
        "inst_code":    current_user.inst_code,
    }


@router.post("/logout")
async def logout():
    """JWT는 stateless — 클라이언트에서 토큰 삭제."""
    return {"message": "로그아웃되었습니다"}


# ── A-D2: MFA (TOTP) 설정 엔드포인트 ─────────────────────────────────────────

@router.get("/mfa/status")
async def mfa_status(current_user: User = Depends(get_current_user)):
    """현재 사용자의 MFA 활성화 여부 조회."""
    return {
        "username":    current_user.username,
        "mfa_enabled": current_user.mfa_enabled,
    }


@router.post("/mfa/setup")
async def mfa_setup(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    MFA 등록 시작 — TOTP 시크릿 + QR 코드 생성.

    새 시크릿을 생성하여 DB에 (미활성 상태로) 저장.
    반환된 QR 코드를 앱으로 스캔 후 /mfa/enable 로 활성화.
    """
    from core.mfa import (
        generate_totp_secret, get_totp_uri,
        generate_qr_base64, encrypt_totp_secret,
    )

    secret = generate_totp_secret()
    uri    = get_totp_uri(current_user.username, secret)
    qr_b64 = generate_qr_base64(uri)

    # 시크릿 암호화 후 DB 저장 (mfa_enabled는 아직 False)
    result = await db.execute(select(User).where(User.id == current_user.id))
    user = result.scalars().first()
    user.totp_secret_enc = encrypt_totp_secret(secret)
    await db.commit()

    return {
        "message":   "QR 코드를 앱(Google Authenticator 등)으로 스캔하세요.",
        "qr_uri":    uri,
        "qr_base64": qr_b64,     # None 이면 qr_uri 직접 입력
        "secret":    secret,     # 앱 수동 입력용 (화면 표시 후 숨김)
    }


@router.post("/mfa/enable")
async def mfa_enable(
    body: MfaSetupRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    MFA 활성화 — 앱에서 생성된 TOTP 코드로 설정 확인.

    /mfa/setup 후 앱에서 생성된 코드를 입력하여 MFA를 활성화한다.
    이미 MFA가 활성화된 경우 400 반환.
    """
    from core.mfa import decrypt_totp_secret, verify_totp

    if current_user.mfa_enabled:
        raise HTTPException(400, "MFA가 이미 활성화되어 있습니다.")
    if not current_user.totp_secret_enc:
        raise HTTPException(400, "MFA 시크릿이 없습니다. 먼저 /mfa/setup을 호출하세요.")

    try:
        secret = decrypt_totp_secret(current_user.totp_secret_enc)
    except Exception:
        raise HTTPException(500, "MFA 시크릿 복호화 실패.")

    if not verify_totp(secret, body.totp_code):
        raise HTTPException(400, "TOTP 코드가 올바르지 않습니다. 앱을 확인하세요.")

    result = await db.execute(select(User).where(User.id == current_user.id))
    user = result.scalars().first()
    user.mfa_enabled = True
    await db.commit()

    return {"message": "MFA가 활성화되었습니다.", "mfa_enabled": True}


@router.post("/mfa/disable")
async def mfa_disable(
    body: MfaDisableRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    MFA 비활성화 — 비밀번호 + TOTP 코드 이중 확인.

    ADMIN은 자신의 MFA만 비활성화 가능.
    타 사용자의 MFA 강제 비활성화는 /admin/users/{id}/mfa-reset 사용.
    """
    from core.mfa import decrypt_totp_secret, verify_totp

    if not current_user.mfa_enabled:
        raise HTTPException(400, "MFA가 활성화되어 있지 않습니다.")

    if not verify_password(body.password, current_user.hashed_pw):
        raise HTTPException(401, "비밀번호가 올바르지 않습니다.")

    try:
        secret = decrypt_totp_secret(current_user.totp_secret_enc)
        if not verify_totp(secret, body.totp_code):
            raise HTTPException(400, "TOTP 코드가 올바르지 않습니다.")
    except HTTPException:
        raise
    except Exception:
        raise HTTPException(500, "MFA 검증 중 오류가 발생했습니다.")

    result = await db.execute(select(User).where(User.id == current_user.id))
    user = result.scalars().first()
    user.mfa_enabled     = False
    user.totp_secret_enc = None
    await db.commit()

    return {"message": "MFA가 비활성화되었습니다.", "mfa_enabled": False}


@router.post("/admin/users/{user_id}/mfa-reset", status_code=200)
async def admin_mfa_reset(
    user_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    관리자 전용: 특정 사용자의 MFA 강제 초기화.
    ADMIN 권한 필요 (본인 제외).
    사용자가 MFA 앱을 분실했거나 접근 불가 시 사용.
    """
    if current_user.role != "ADMIN":
        raise HTTPException(403, "ADMIN 권한이 필요합니다.")

    result = await db.execute(select(User).where(User.id == user_id))
    target = result.scalars().first()
    if not target:
        raise HTTPException(404, "사용자를 찾을 수 없습니다.")
    if target.id == current_user.id:
        raise HTTPException(400, "자신의 MFA는 /mfa/disable 을 사용하세요.")

    target.mfa_enabled     = False
    target.totp_secret_enc = None
    await db.commit()

    return {
        "message":       f"{target.username}의 MFA가 초기화되었습니다.",
        "reset_by":      current_user.username,
        "target_user":   target.username,
    }


@router.get("/users")
async def list_users(
    current_user: User = Depends(get_current_user),
    db: AsyncSession   = Depends(get_db),
):
    """관리자 전용: 전체 사용자 목록."""
    if current_user.role != "ADMIN":
        raise HTTPException(status_code=403, detail="관리자만 접근 가능합니다")
    result = await db.execute(select(User).order_by(User.id))
    users  = result.scalars().all()
    return [
        {
            "id":           u.id,
            "username":     u.username,
            "display_name": u.display_name,
            "role":         u.role,
            "is_active":    u.is_active,
            "must_change_pw": u.must_change_pw,
            "last_login_at": u.last_login_at.isoformat() if u.last_login_at else None,
        }
        for u in users
    ]

@router.post("/admin/users/{user_id}/unlock", status_code=200)
async def admin_unlock_user(
    user_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """관리자 전용: 잠긴 계정 강제 해제 (ADMIN 권한 필요)."""
    if current_user.role != "ADMIN":
        raise HTTPException(403, "ADMIN 권한이 필요합니다.")

    result = await db.execute(select(User).where(User.id == user_id))
    target = result.scalars().first()
    if not target:
        raise HTTPException(404, "사용자를 찾을 수 없습니다.")

    target.failed_login_count = 0
    target.locked_until       = None
    await db.commit()

    return {
        "message":      f"{target.username} 계정 잠금이 해제되었습니다.",
        "unlocked_by":  current_user.username,
        "target_user":  target.username,
    }


@router.get("/admin/users/{user_id}/lock-status")
async def admin_user_lock_status(
    user_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """관리자 전용: 사용자 계정 잠금 상태 조회 (ADMIN 권한 필요)."""
    if current_user.role != "ADMIN":
        raise HTTPException(403, "ADMIN 권한이 필요합니다.")

    result = await db.execute(select(User).where(User.id == user_id))
    target = result.scalars().first()
    if not target:
        raise HTTPException(404, "사용자를 찾을 수 없습니다.")

    from datetime import datetime as dt
    is_locked = bool(target.locked_until and target.locked_until > dt.now())
    remaining_min = 0
    if is_locked:
        remaining_min = int((target.locked_until - dt.now()).total_seconds() / 60) + 1

    return {
        "username":            target.username,
        "is_locked":           is_locked,
        "failed_login_count":  target.failed_login_count or 0,
        "locked_until":        target.locked_until.isoformat() if target.locked_until else None,
        "remaining_minutes":   remaining_min,
    }


# ════════════════════════════════════════════════════════════════════════════════
# ── 모바일 100기능: 디바이스 / 보안 이벤트 / 네트워크 상태 / 기관 전환 ───────────
# ════════════════════════════════════════════════════════════════════════════════

class DeviceRegister(BaseModel):
    device_name: Optional[str] = None
    device_type: Optional[str] = None   # android | ios | web
    push_token: Optional[str] = None


class SwitchTenantRequest(BaseModel):
    tenant_id: str   # 전환 대상 inst_code


@router.get("/devices")
async def list_devices(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """등록 디바이스 목록 (#33)."""
    rows = (await db.execute(
        select(UserDevice).where(UserDevice.username == current_user.username)
        .order_by(UserDevice.last_seen_at.desc())
    )).scalars().all()
    return [
        {
            "id": d.id,
            "device_name": d.device_name,
            "device_type": d.device_type,
            "last_seen_at": d.last_seen_at.isoformat() if d.last_seen_at else None,
            "created_at": d.created_at.isoformat() if d.created_at else None,
        }
        for d in rows
    ]


@router.post("/devices", status_code=201)
async def register_device(
    body: DeviceRegister,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """디바이스 등록/갱신 (#33)."""
    dev = UserDevice(
        username=current_user.username,
        device_name=body.device_name,
        device_type=body.device_type,
        push_token=body.push_token,
        last_seen_at=datetime.now(),
    )
    db.add(dev)
    db.add(LoginEvent(username=current_user.username, event_type="DEVICE_ADDED",
                      detail=f"{body.device_type or '?'} / {body.device_name or '?'}"))
    await db.commit()
    await db.refresh(dev)
    return {"id": dev.id, "message": "디바이스가 등록되었습니다."}


@router.delete("/devices/{device_id}", status_code=204)
async def unregister_device(
    device_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """디바이스 등록 해제 (#33)."""
    dev = await db.get(UserDevice, device_id)
    if not dev or dev.username != current_user.username:
        raise HTTPException(404, "디바이스를 찾을 수 없습니다.")
    await db.delete(dev)
    db.add(LoginEvent(username=current_user.username, event_type="DEVICE_REMOVED",
                      detail=f"device_id={device_id}"))
    await db.commit()


@router.get("/events")
async def list_security_events(
    limit: int = 50,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """보안 이벤트 로그 — 로그인 이력/실패/디바이스 변경 등 (#34)."""
    rows = (await db.execute(
        select(LoginEvent).where(LoginEvent.username == current_user.username)
        .order_by(LoginEvent.created_at.desc())
        .limit(min(limit, 200))
    )).scalars().all()
    return [
        {
            "id": e.id,
            "event_type": e.event_type,
            "detail": e.detail,
            "created_at": e.created_at.isoformat() if e.created_at else None,
        }
        for e in rows
    ]


@router.get("/network-status")
async def network_status(
    current_user: User = Depends(get_current_user),
):
    """접속 경로 상태 — VPN / 개방망 / 내부망 (#37)."""
    import os as _os
    mode = _os.environ.get("GUARDIA_NETWORK_MODE", "internal")
    if mode == "open":
        via, level = "opennet", 2
    elif mode == "vpn":
        via, level = "vpn", 2
    else:
        via, level = "internal", 3   # 내부망이 가장 신뢰 수준 높음
    return {
        "via": via,
        "level": level,
        "username": current_user.username,
        "checked_at": datetime.now().isoformat(),
    }


@router.post("/switch-tenant")
async def switch_tenant(
    body: SwitchTenantRequest,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """기관 전환 — 새 inst_code로 전환 후 새 JWT 발급 (#33/멀티기관)."""
    inst = (await db.execute(
        select(Institution).where(Institution.inst_code == body.tenant_id)
    )).scalars().first()
    if not inst:
        raise HTTPException(404, "전환 대상 기관을 찾을 수 없습니다.")

    # CUSTOMER는 자기 기관으로만 제한, ADMIN/PM/ENGINEER는 자유 전환
    from models import UserRole
    if current_user.role == UserRole.CUSTOMER and current_user.inst_code != body.tenant_id:
        raise HTTPException(403, "해당 기관으로 전환할 권한이 없습니다.")

    token = create_access_token({
        "sub": current_user.username,
        "role": current_user.role,
        "tenant": body.tenant_id,
    })
    db.add(LoginEvent(username=current_user.username, event_type="TENANT_SWITCH",
                      detail=f"→ {body.tenant_id}"))
    await db.commit()
    return {
        "access_token": token,
        "token_type": "bearer",
        "tenant_id": body.tenant_id,
        "inst_name": inst.inst_name,
    }


# ── OAuth2 소셜 로그인 ────────────────────────────────────────────────────────

@router.get("/oauth/providers")
async def oauth_providers():
    """활성화된 OAuth 제공자 목록 반환 (로그인 페이지에서 버튼 표시 여부 결정)."""
    from core.oauth import get_enabled_providers
    return {"providers": get_enabled_providers()}


@router.get("/oauth/{provider}/start")
async def oauth_start(provider: str):
    """OAuth 인증 흐름 시작 — 제공자 인증 페이지로 리디렉트."""
    from core.oauth import build_auth_url, PROVIDERS
    from fastapi.responses import RedirectResponse

    if provider not in PROVIDERS or not PROVIDERS[provider]["enabled"]:
        raise HTTPException(400, f"OAuth 제공자 '{provider}'가 설정되지 않았습니다.")

    url = build_auth_url(provider)
    return RedirectResponse(url)


@router.get("/oauth/{provider}/callback")
async def oauth_callback(
    provider: str,
    code: str = "",
    state: str = "",
    error: str = "",
    db: AsyncSession = Depends(get_db),
):
    """
    OAuth 콜백 처리 — 인가 코드로 토큰 교환 후 GUARDiA 계정과 연결.

    - 이메일 기반으로 기존 계정 조회 또는 신규 계정 자동 생성 (auth_type=oauth)
    - 성공 시 /login 페이지로 리디렉트하며 JWT 토큰을 URL 파라미터로 전달
    """
    from core.oauth import verify_state, exchange_code, extract_email, extract_name, PROVIDERS
    from fastapi.responses import RedirectResponse

    LOGIN_FAIL_URL = "/login?error=oauth_failed"

    if error:
        return RedirectResponse(f"/login?error={error}")

    if not verify_state(state):
        return RedirectResponse(f"{LOGIN_FAIL_URL}&reason=state_mismatch")

    if provider not in PROVIDERS or not PROVIDERS[provider]["enabled"]:
        return RedirectResponse(f"{LOGIN_FAIL_URL}&reason=unknown_provider")

    userinfo = await exchange_code(provider, code)
    if not userinfo:
        return RedirectResponse(f"{LOGIN_FAIL_URL}&reason=token_exchange")

    email = extract_email(provider, userinfo)
    if not email:
        return RedirectResponse(f"{LOGIN_FAIL_URL}&reason=no_email")

    # 기존 계정 조회 (이메일 기준)
    result = await db.execute(select(User).where(User.email == email, User.is_active == True))
    user = result.scalars().first()

    if not user:
        # 자동 계정 생성 (CUSTOMER 역할, 비밀번호 없음)
        display = extract_name(provider, userinfo)
        username = email.split("@")[0].replace(".", "_")[:30]
        # username 중복 처리
        existing = (await db.execute(select(User).where(User.username == username))).scalars().first()
        if existing:
            username = f"{username}_{provider[:3]}"

        user = User(
            username      = username,
            display_name  = display or username,
            email         = email,
            hashed_pw     = "",  # OAuth 계정은 비밀번호 없음
            role          = "CUSTOMER",
            is_active     = True,
            must_change_pw= False,
            auth_type     = f"oauth:{provider}",
        )
        db.add(user)
        await db.flush()

    user.last_login_at = datetime.now()
    await db.commit()

    token = create_access_token({"sub": user.username, "role": user.role})
    # 토큰을 URL 파라미터로 전달 → 로그인 페이지 JS에서 처리
    return RedirectResponse(f"/login?oauth_token={token}&username={user.username}&role={user.role}")
