"""
# Authentication Router
#
# Endpoints for Login, Logout, Token-Refresh and User-Management.
"""
import hmac
import os
from datetime import datetime, timedelta
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status, Request
from fastapi.security import OAuth2PasswordRequestForm
from fastapi.responses import JSONResponse
from sqlalchemy.orm import Session
from pydantic import BaseModel, EmailStr

from app.database import get_db
from app.models.user import User, UserRole
from app.models.audit_log import AuditLog, AuditEventType
from app.auth.jwt_handler import (
    hash_password,
    verify_password,
    create_access_token,
    create_refresh_token,
    decode_token,
    get_password_strength,
    JWT_ACCESS_TOKEN_EXPIRE_MINUTES,
    JWT_REFRESH_TOKEN_EXPIRE_DAYS
)
from app.auth.dependencies import get_current_user, RequireAdmin
from app.auth.orchestrator import AuthOrchestrator
from app.auth.strategies.base import AuthError
from app.auth import totp as totp_helper
from app.auth.token_revocation import revoke as revoke_token
from app.models.user import AuthProvider
from app.rate_limiter import limiter

COOKIE_SAMESITE = os.getenv("AUTH_COOKIE_SAMESITE", "lax")
COOKIE_SECURE_ENV = os.getenv("AUTH_COOKIE_SECURE")
COOKIE_SECURE = (
    COOKIE_SECURE_ENV.lower() == "true"
    if COOKIE_SECURE_ENV is not None
    else os.getenv("ENV", "production") != "development"
)
SETUP_ADMIN_TOKEN = os.getenv("SETUP_ADMIN_TOKEN")
ALLOW_ADMIN_RESET = os.getenv("ALLOW_ADMIN_RESET", "false").lower() == "true"


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


# ============================================
# Pydantic Schemas
# ============================================

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


class LoginResponse(BaseModel):
    access_token: str
    refresh_token: str
    token_type: str = "bearer"
    user: dict
    mfa_required: bool = False           # POST /auth/mfa/verify next
    mfa_setup_required: bool = False     # enrolment forced by policy → POST /auth/mfa/forced-setup/*


class MfaVerifyRequest(BaseModel):
    mfa_token: str  # short-lived token issued when login produced mfa_required=true
    code: str


class MfaSetupRequest(BaseModel):
    password: str  # require current password to confirm before enabling


class MfaSetupResponse(BaseModel):
    secret: str           # base32 (one-time, only shown during setup)
    provisioning_uri: str # otpauth:// for QR rendering


class MfaActivateRequest(BaseModel):
    code: str  # confirm user has scanned the QR before persisting


class MfaForcedSetupStartRequest(BaseModel):
    setup_token: str  # the short-lived token issued by /auth/login when mfa_setup_required=true


class MfaForcedSetupActivateRequest(BaseModel):
    setup_token: str
    code: str  # first TOTP code from the just-scanned QR


class RefreshTokenRequest(BaseModel):
    refresh_token: str


class RegisterRequest(BaseModel):
    username: str
    email: EmailStr
    password: str
    role: UserRole = UserRole.READONLY


class PasswordChangeRequest(BaseModel):
    old_password: str
    new_password: str


class SetupAdminRequest(BaseModel):
    password: str
    username: Optional[str] = None
    email: Optional[EmailStr] = None


# ============================================
# Helper Functions
# ============================================

def log_audit_event(
    db: Session,
    event_type: AuditEventType,
    description: str,
    user_id: int = None,
    request: Request = None
):
    """Logs audit event to DB"""
    audit_log = AuditLog(
        user_id=user_id,
        event_type=event_type,
        event_description=description,
        ip_address=request.client.host if request else None,
        user_agent=request.headers.get("user-agent") if request else None,
        timestamp=datetime.now()
    )
    db.add(audit_log)
    db.commit()


def _mfa_eligible(user: User) -> bool:
    """
    Which auth providers can enrol in our app-side TOTP?

    - LOCAL: yes — verify password against password_hash
    - LDAP:  yes — verify password by re-binding to LDAP
    - SAML / OIDC: no — the IdP must own MFA; layering app-side TOTP on
      top would require the user to type a password we never collect.
    """
    return user.auth_provider in (AuthProvider.LOCAL, AuthProvider.LDAP)


def _verify_user_password(db: Session, user: User, password: str) -> bool:
    """
    Verify a password against the user's backing auth provider.

    Used by MFA setup / disable for the "confirm your password" step.
    Returns True only on a successful credential check; any other path
    (wrong password, LDAP unreachable, provider not eligible) is False.
    """
    if not password:
        return False

    if user.auth_provider == AuthProvider.LOCAL:
        return bool(user.password_hash) and verify_password(password, user.password_hash)

    if user.auth_provider == AuthProvider.LDAP:
        try:
            from app.auth.strategies.ldap_strategy import LdapStrategy
            strat = LdapStrategy(db)
            if not strat.is_enabled:
                return False
            # authenticate_credentials raises AuthError on bad password / not found
            strat.authenticate_credentials(user.username, password)
            return True
        except Exception:
            return False

    # SAML / OIDC: no app-side password — never accept here.
    return False


def check_account_lockout(user: User, db: Session) -> bool:
    """
    Checks if account is locked (after 5 failed logins).
    Auto-unlocks after 15 minutes.

    Returns:
        True if locked
    """
    if user.failed_login_attempts >= 5:
        if user.updated_at and (datetime.now() - user.updated_at) > timedelta(minutes=15):
            user.failed_login_attempts = 0
            db.commit()
            return False
        return True
    return False


def _set_auth_cookies(response: JSONResponse, access_token: str, refresh_token: str) -> None:
    response.set_cookie(
        "access_token",
        access_token,
        httponly=True,
        secure=COOKIE_SECURE,
        samesite=COOKIE_SAMESITE,
        max_age=JWT_ACCESS_TOKEN_EXPIRE_MINUTES * 60,
        path="/",
    )
    response.set_cookie(
        "refresh_token",
        refresh_token,
        httponly=True,
        secure=COOKIE_SECURE,
        samesite=COOKIE_SAMESITE,
        max_age=JWT_REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60,
        path="/",
    )


# ============================================
# Endpoints
# ============================================

def _issue_session_response(user: User, db: Session) -> JSONResponse:
    """Generate JWTs and JSON response payload for a successfully authenticated user."""
    token_data = {"sub": user.username, "role": user.role.value}
    access_token = create_access_token(token_data)
    refresh_token = create_refresh_token(token_data)
    response = JSONResponse(content={
        "access_token": access_token,
        "refresh_token": refresh_token,
        "token_type": "bearer",
        "user": {
            "id": user.id,
            "username": user.username,
            "email": user.email,
            "role": user.role.value,
            "auth_provider": user.auth_provider.value,
            "mfa_enabled": user.totp_enabled,
        },
        "mfa_required": False,
    })
    _set_auth_cookies(response, access_token, refresh_token)
    return response


def _issue_mfa_challenge(user: User) -> JSONResponse:
    """Short-lived (5 min) MFA token. Client must POST /auth/mfa/verify with a TOTP code."""
    mfa_token = create_access_token(
        {"sub": user.username, "mfa_pending": True},
        expires_delta=timedelta(minutes=5),
    )
    return JSONResponse(content={
        "access_token": "",
        "refresh_token": "",
        "token_type": "mfa_pending",
        "user": {"id": user.id, "username": user.username},
        "mfa_required": True,
        "mfa_token": mfa_token,
    })


def _issue_forced_setup_challenge(user: User) -> JSONResponse:
    """
    Issued when MFA-enforcement policy demands enrolment before any session.

    Returns a short-lived (10 min) token the frontend uses against the
    two-step forced-setup endpoints. NO access/refresh token is issued.
    """
    setup_token = create_access_token(
        {"sub": user.username, "mfa_setup_pending": True},
        expires_delta=timedelta(minutes=10),
    )
    return JSONResponse(content={
        "access_token": "",
        "refresh_token": "",
        "token_type": "mfa_setup_pending",
        "user": {
            "id": user.id,
            "username": user.username,
            "auth_provider": user.auth_provider.value,
        },
        "mfa_required": False,
        "mfa_setup_required": True,
        "setup_token": setup_token,
    })


@router.post("/login", response_model=LoginResponse)
@limiter.limit("5/minute")
async def login(
    request: Request,
    credentials: LoginRequest,
    db: Session = Depends(get_db)
):
    """
    Username/password login routed through the AuthOrchestrator.

    Strategy chain configurable via AUTH_LOOKUP_ORDER (default: local,ldap).
    SSO providers (SAML/OIDC) have their own routers — they do not use this endpoint.

    Security properties:
    - Generic 'Invalid credentials' for every failure mode (anti-enumeration)
    - Rate-limited (5/min/IP via slowapi)
    - Failed attempts increment per-user counter; account lockout at 5
    - Account-lockout check happens inside the LocalAuthStrategy
    - Audit-logged via orchestrator (LOGIN_SUCCESS / LOGIN_LDAP_SUCCESS / LOGIN_FAILED)
    """
    ip = request.client.host if request.client else None
    ua = request.headers.get("user-agent")

    orchestrator = AuthOrchestrator(db)

    try:
        result = orchestrator.authenticate_with_credentials(
            credentials.username, credentials.password,
            actor_ip=ip, actor_user_agent=ua,
        )
    except AuthError as e:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=e.safe_message,
            headers={"WWW-Authenticate": "Bearer"},
        )

    if not result.user.is_active:
        log_audit_event(
            db,
            AuditEventType.ACCESS_DENIED,
            "Login attempt for deactivated account",
            user_id=result.user.id,
            request=request,
        )
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Account is deactivated",
        )

    # Forced-setup short-circuits everything else — admin or editor with
    # no TOTP and the global `mfa_enforced` toggle on must enrol RIGHT
    # NOW before getting any session.
    if result.mfa_setup_required:
        return _issue_forced_setup_challenge(result.user)

    if result.mfa_required:
        return _issue_mfa_challenge(result.user)

    return _issue_session_response(result.user, db)


@router.post("/mfa/verify", response_model=LoginResponse)
@limiter.limit("10/minute")
async def mfa_verify(
    request: Request,
    payload: MfaVerifyRequest,
    db: Session = Depends(get_db)
):
    """
    Second-factor verification step. Client supplies mfa_token (from /auth/login)
    plus 6-digit TOTP code. Returns full session on success.
    """
    try:
        decoded = decode_token(payload.mfa_token)
    except Exception:
        raise HTTPException(status_code=401, detail="Invalid or expired MFA token")
    if not decoded.get("mfa_pending"):
        raise HTTPException(status_code=401, detail="Invalid MFA token")

    user = db.query(User).filter(User.username == decoded.get("sub")).first()
    if not user or not user.is_active:
        raise HTTPException(status_code=401, detail="Invalid MFA token")

    # Pass `db` so legacy plain-base32 secrets get re-encrypted on the
    # first successful verify after the AUTH_PROVIDER_CRYPTO_KEY upgrade.
    if not totp_helper.verify_for_user(user, payload.code, db=db):
        log_audit_event(
            db, AuditEventType.MFA_FAILED,
            f"MFA verify failed for {user.username}",
            user_id=user.id, request=request,
        )
        raise HTTPException(status_code=401, detail="Invalid MFA code")

    log_audit_event(
        db, AuditEventType.MFA_VERIFIED,
        f"MFA verified for {user.username}",
        user_id=user.id, request=request,
    )
    return _issue_session_response(user, db)


@router.get("/providers")
async def list_auth_providers():
    """
    Public endpoint: lists enabled auth providers so the frontend can render
    the correct mix of login buttons (local form, LDAP shares the local form,
    OIDC/SAML get their own redirect buttons).
    """
    enabled = [p.strip().lower() for p in os.getenv("AUTH_PROVIDERS", "local").split(",") if p.strip()]
    return {
        "providers": [
            {
                "name": p,
                "credentials": p in ("local", "ldap"),
                "redirect": p in ("saml", "oidc"),
                "display_name": {
                    "local": "Local account",
                    "ldap": "Company directory (LDAP)",
                    "oidc": os.getenv("OIDC_PROVIDER_NAME", "Single Sign-On"),
                    "saml": os.getenv("SAML_PROVIDER_NAME", "SAML SSO"),
                }.get(p, p),
                "login_url": {
                    "oidc": "/auth/oidc/login",
                    "saml": "/auth/saml/login",
                }.get(p),
            }
            for p in enabled
        ],
    }


@router.post("/mfa/setup", response_model=MfaSetupResponse)
@limiter.limit("5/minute")
async def mfa_setup(
    request: Request,
    payload: MfaSetupRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """
    Start TOTP enrolment. Returns a new secret + provisioning URI for the
    user to scan with their authenticator app. Secret is NOT yet persisted;
    user must confirm via /auth/mfa/activate with a fresh code.

    Available for local AND LDAP accounts. SAML / OIDC users get MFA from
    their IdP and are rejected here.
    """
    if not _mfa_eligible(current_user):
        raise HTTPException(
            status_code=400,
            detail=(
                f"MFA setup not available for {current_user.auth_provider.value} accounts — "
                "your identity provider handles MFA."
            ),
        )
    if current_user.totp_enabled:
        raise HTTPException(status_code=400, detail="MFA is already enabled. Disable it first to reset.")

    if not _verify_user_password(db, current_user, payload.password):
        log_audit_event(db, AuditEventType.MFA_FAILED, "MFA setup denied: bad password",
                        user_id=current_user.id, request=request)
        raise HTTPException(status_code=401, detail="Invalid credentials")

    secret = totp_helper.generate_secret()
    uri = totp_helper.provisioning_uri(current_user.username, secret)

    # Stash encrypted secret pending activation. totp_enabled stays False
    # until /auth/mfa/activate succeeds.
    current_user.totp_secret = totp_helper.encrypt_secret(secret)
    current_user.totp_enabled = False
    db.commit()

    return MfaSetupResponse(secret=secret, provisioning_uri=uri)


@router.post("/mfa/activate")
@limiter.limit("10/minute")
async def mfa_activate(
    request: Request,
    payload: MfaActivateRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Confirm TOTP enrolment by verifying a code against the pending secret."""
    if not _mfa_eligible(current_user):
        raise HTTPException(
            status_code=400,
            detail=(
                f"MFA not available for {current_user.auth_provider.value} accounts — "
                "your identity provider handles MFA."
            ),
        )
    if not current_user.totp_secret:
        raise HTTPException(status_code=400, detail="No pending MFA enrolment. Call /auth/mfa/setup first.")
    try:
        secret = totp_helper.decrypt_secret(current_user.totp_secret)
    except Exception:
        raise HTTPException(status_code=500, detail="MFA secret unreadable. Restart enrolment.")

    if not totp_helper.verify(secret, payload.code):
        log_audit_event(db, AuditEventType.MFA_FAILED, "MFA activation: bad code",
                        user_id=current_user.id, request=request)
        raise HTTPException(status_code=401, detail="Invalid MFA code")

    current_user.totp_enabled = True
    db.commit()
    log_audit_event(db, AuditEventType.MFA_ENABLED, "MFA enabled",
                    user_id=current_user.id, request=request)
    return {"message": "MFA enabled successfully"}


@router.post("/mfa/disable")
@limiter.limit("5/minute")
async def mfa_disable(
    request: Request,
    payload: MfaSetupRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Disable TOTP. Requires password confirmation against the user's auth
    provider (local password_hash, or LDAP rebind)."""
    if not _mfa_eligible(current_user):
        raise HTTPException(
            status_code=400,
            detail="MFA disable not available for this account type.",
        )
    if not _verify_user_password(db, current_user, payload.password):
        log_audit_event(db, AuditEventType.MFA_FAILED, "MFA disable denied: bad password",
                        user_id=current_user.id, request=request)
        raise HTTPException(status_code=401, detail="Invalid credentials")
    current_user.totp_enabled = False
    current_user.totp_secret = None
    db.commit()
    log_audit_event(db, AuditEventType.MFA_DISABLED, "MFA disabled",
                    user_id=current_user.id, request=request)
    return {"message": "MFA disabled"}


# ============================================
# Forced MFA enrolment (driven by mfa_policy)
# ============================================

def _decode_setup_token(setup_token: str, db: Session) -> User:
    """
    Validate a `mfa_setup_pending` short-lived token. Returns the User
    row or raises 401. Used by both forced-setup endpoints below.
    """
    try:
        decoded = decode_token(setup_token)
    except Exception:
        raise HTTPException(status_code=401, detail="Invalid or expired setup token")
    if not decoded.get("mfa_setup_pending"):
        raise HTTPException(status_code=401, detail="Invalid setup token")
    user = db.query(User).filter(User.username == decoded.get("sub")).first()
    if not user or not user.is_active:
        raise HTTPException(status_code=401, detail="Invalid setup token")
    return user


@router.post("/mfa/forced-setup/start", response_model=MfaSetupResponse)
@limiter.limit("5/minute")
async def mfa_forced_setup_start(
    request: Request,
    payload: MfaForcedSetupStartRequest,
    db: Session = Depends(get_db),
):
    """
    Begin forced TOTP enrolment for a user who was bounced at /auth/login
    with mfa_setup_required=true. No JWT yet. Returns the QR secret
    only — the secret is persisted (encrypted) but totp_enabled stays
    False until /forced-setup/activate succeeds.
    """
    user = _decode_setup_token(payload.setup_token, db)
    if user.totp_enabled:
        # Race: another tab finished setup. Reject cleanly so the user
        # is sent back through normal login.
        raise HTTPException(status_code=400, detail="MFA already enabled — please log in again")

    secret = totp_helper.generate_secret()
    uri = totp_helper.provisioning_uri(user.username, secret)

    user.totp_secret = totp_helper.encrypt_secret(secret)
    user.totp_enabled = False
    db.commit()

    log_audit_event(
        db, AuditEventType.MFA_FAILED,  # MFA_PENDING would be better — re-use FAILED until enum migrated
        f"Forced MFA enrolment started for {user.username}",
        user_id=user.id, request=request,
    )

    return MfaSetupResponse(secret=secret, provisioning_uri=uri)


@router.post("/mfa/forced-setup/activate", response_model=LoginResponse)
@limiter.limit("10/minute")
async def mfa_forced_setup_activate(
    request: Request,
    payload: MfaForcedSetupActivateRequest,
    db: Session = Depends(get_db),
):
    """
    Confirm forced enrolment with a fresh TOTP code. On success, marks
    totp_enabled=true and IMMEDIATELY issues a full session — the user
    does not need to re-enter their password.
    """
    user = _decode_setup_token(payload.setup_token, db)
    if not user.totp_secret:
        raise HTTPException(status_code=400, detail="No pending enrolment — call /forced-setup/start first")

    try:
        secret = totp_helper.decrypt_secret(user.totp_secret)
    except Exception:
        raise HTTPException(status_code=500, detail="MFA secret unreadable. Restart enrolment.")

    if not totp_helper.verify(secret, payload.code):
        log_audit_event(
            db, AuditEventType.MFA_FAILED,
            f"Forced MFA activation: bad code for {user.username}",
            user_id=user.id, request=request,
        )
        raise HTTPException(status_code=401, detail="Invalid MFA code")

    user.totp_enabled = True
    db.commit()
    log_audit_event(
        db, AuditEventType.MFA_ENABLED,
        f"MFA enabled via forced enrolment for {user.username}",
        user_id=user.id, request=request,
    )

    # Bypass the password-re-prompt: we just verified a fresh TOTP code,
    # and the user already authenticated with their password seconds ago.
    return _issue_session_response(user, db)


@router.post("/logout")
async def logout(
    request: Request,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """
    User logout.

    Revokes the access token (cookie or Authorization header) and any
    accompanying refresh token by JTI. Subsequent calls bearing those
    tokens are rejected by the get_current_user / refresh dependencies.
    """
    # Revoke access token JTI
    bearer = request.headers.get("authorization")
    access_token = None
    if bearer and bearer.lower().startswith("bearer "):
        access_token = bearer.split(" ", 1)[1].strip()
    if not access_token:
        access_token = request.cookies.get("access_token")
    if access_token:
        payload = decode_token(access_token)
        if payload and payload.get("jti"):
            revoke_token(payload["jti"], payload.get("exp"))

    # Revoke refresh token JTI from cookie (the body-supplied refresh path
    # passes the token through /auth/refresh, not /auth/logout).
    refresh_cookie = request.cookies.get("refresh_token")
    if refresh_cookie:
        payload = decode_token(refresh_cookie)
        if payload and payload.get("jti"):
            revoke_token(payload["jti"], payload.get("exp"))

    log_audit_event(
        db,
        AuditEventType.LOGOUT,
        "User logged out",
        user_id=current_user.id,
        request=request
    )

    response = JSONResponse(content={"message": "Successfully logged out"})
    response.delete_cookie("access_token", path="/")
    response.delete_cookie("refresh_token", path="/")
    return response


@router.post("/refresh", response_model=LoginResponse)
async def refresh_token(
    refresh_request: RefreshTokenRequest,
    db: Session = Depends(get_db)
):
    """
    Renew access token with refresh token

    Security:
        - Checks token type (must be "refresh")
        - Validates token signature and expiry
    """
    payload = decode_token(refresh_request.refresh_token)

    if not payload or payload.get("type") != "refresh":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid refresh token"
        )

    from app.auth.token_revocation import is_revoked as _is_revoked
    jti = payload.get("jti")
    if jti and _is_revoked(jti):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid refresh token"
        )

    # Rotate: revoke the presented refresh token so it cannot be reused.
    if jti:
        revoke_token(jti, payload.get("exp"))

    username = payload.get("sub")
    user = db.query(User).filter(User.username == username).first()

    if not user or not user.is_active:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="User not found or deactivated"
        )

    # Generate new access token
    token_data = {"sub": user.username, "role": user.role.value}
    access_token = create_access_token(token_data)
    new_refresh_token = create_refresh_token(token_data)

    response = JSONResponse(content={
        "access_token": access_token,
        "refresh_token": new_refresh_token,
        "token_type": "bearer",
        "user": {
            "id": user.id,
            "username": user.username,
            "email": user.email,
            "role": user.role.value
        }
    })
    _set_auth_cookies(response, access_token, new_refresh_token)
    return response


@router.post("/setup-admin")
@limiter.limit("3/minute")
async def setup_admin(
    request: Request,
    setup: SetupAdminRequest,
    db: Session = Depends(get_db)
):
    """
    Emergency setup/reset for admin user.
    Requires SETUP_ADMIN_TOKEN to be set and passed via X-Setup-Token header.
    """
    if not SETUP_ADMIN_TOKEN:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not found")

    token = request.headers.get("X-Setup-Token")
    if not token or not hmac.compare_digest(token, SETUP_ADMIN_TOKEN):
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid setup token")

    any_admin_exists = db.query(User).filter(User.role == UserRole.ADMIN).count() > 0
    if any_admin_exists and not ALLOW_ADMIN_RESET:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Admin already exists. Set ALLOW_ADMIN_RESET=true to reset."
        )

    password_check = get_password_strength(setup.password)
    if not password_check["is_valid"]:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Password too weak: {', '.join(password_check['feedback'])}"
        )

    username = setup.username or os.getenv("DEFAULT_ADMIN_USERNAME", "admin")
    email = setup.email or os.getenv("DEFAULT_ADMIN_EMAIL", "admin@vulnmanager.local")

    user = db.query(User).filter(User.username == username).first()
    if user:
        user.password_hash = hash_password(setup.password)
        user.role = UserRole.ADMIN
        user.is_active = True
        if email:
            user.email = email
        action = "reset"
    else:
        user = User(
            username=username,
            email=email,
            password_hash=hash_password(setup.password),
            role=UserRole.ADMIN,
            is_active=True,
            is_verified=True,
            failed_login_attempts=0
        )
        db.add(user)
        action = "created"

    db.commit()

    log_audit_event(
        db,
        AuditEventType.SECURITY_ALERT,
        f"Admin user {action} via setup token: {username}",
        request=request
    )

    return {"status": "ok", "action": action, "username": username}


@router.get("/me")
async def get_current_user_info(current_user: User = Depends(get_current_user)):
    """
    Returns information about current user
    """
    return {
        "id": current_user.id,
        "username": current_user.username,
        "email": current_user.email,
        "role": current_user.role.value,
        "auth_provider": current_user.auth_provider.value if current_user.auth_provider else "local",
        "mfa_enabled": bool(current_user.totp_enabled),
        "is_active": current_user.is_active,
        "is_verified": current_user.is_verified,
        "created_at": current_user.created_at,
    }


@router.post("/register", status_code=status.HTTP_201_CREATED)
async def register_user(
    request: Request,
    user_data: RegisterRequest,
    db: Session = Depends(get_db),
    admin_user: User = Depends(RequireAdmin)  # Nur Admins können User erstellen
):
    """
    Registers new user (Admin-only)

    Security:
        - Password strength check (OWASP)
        - Unique Username/Email
        - Audit logging
    """
    # Check Username/Email duplicates
    if db.query(User).filter(User.username == user_data.username).first():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Username already taken"
        )

    if db.query(User).filter(User.email == user_data.email).first():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Email already registered"
        )

    # Check password strength
    password_check = get_password_strength(user_data.password)
    if not password_check["is_valid"]:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Password too weak: {', '.join(password_check['feedback'])}"
        )

    # Create user
    new_user = User(
        username=user_data.username,
        email=user_data.email,
        password_hash=hash_password(user_data.password),
        role=user_data.role,
        is_active=True,
        is_verified=False
    )

    db.add(new_user)
    db.commit()
    db.refresh(new_user)

    # Audit-Log
    log_audit_event(
        db,
        AuditEventType.PERMISSION_ESCALATION,
        f"New user created: {new_user.username} (Role: {new_user.role.value})",
        user_id=admin_user.id,
        request=request
    )

    return {
        "id": new_user.id,
        "username": new_user.username,
        "email": new_user.email,
        "role": new_user.role.value
    }


@router.post("/change-password")
async def change_password(
    request: Request,
    password_data: PasswordChangeRequest,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """
    Changes password of current user

    Security:
        - Verifies old password
        - Checks new password strength
        - Audit logging
    """
    # Verify old password
    if not verify_password(password_data.old_password, current_user.password_hash):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Old password is incorrect"
        )

    # Check new password strength
    password_check = get_password_strength(password_data.new_password)
    if not password_check["is_valid"]:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"New password too weak: {', '.join(password_check['feedback'])}"
        )

    # Update password
    current_user.password_hash = hash_password(password_data.new_password)
    db.commit()

    # Audit-Log
    log_audit_event(
        db,
        AuditEventType.PASSWORD_CHANGE,
        "Password changed",
        user_id=current_user.id,
        request=request
    )


class AdminResetPasswordRequest(BaseModel):
    new_password: str


@router.post("/users/{user_id}/reset-mfa")
async def admin_reset_mfa(
    request: Request,
    user_id: int,
    current_user: User = Depends(RequireAdmin),
    db: Session = Depends(get_db),
):
    """
    Admin force-reset of a user's TOTP/MFA enrolment.

    Use when a user lost their authenticator device and can no longer
    complete the second-factor step at login. Clears both totp_enabled
    and totp_secret — the user must re-enrol via /auth/mfa/setup on
    their next login.
    """
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
        )

    had_mfa = bool(user.totp_enabled or user.totp_secret)
    user.totp_enabled = False
    user.totp_secret = None
    db.commit()

    log_audit_event(
        db,
        AuditEventType.MFA_DISABLED,
        f"MFA force-reset for {user.username} by admin {current_user.username}",
        user_id=current_user.id,
        request=request,
    )
    return {
        "message": f"MFA reset for {user.username}",
        "had_mfa": had_mfa,
    }


@router.post("/users/{user_id}/reset-password")
async def admin_reset_password(
    request: Request,
    user_id: int,
    password_data: AdminResetPasswordRequest,
    current_user: User = Depends(RequireAdmin),
    db: Session = Depends(get_db)
):
    """
    Admin force reset for user password
    """
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User not found"
        )

    # Check password strength
    password_check = get_password_strength(password_data.new_password)
    if not password_check["is_valid"]:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Password too weak: {', '.join(password_check['feedback'])}"
        )

    # Update password
    user.password_hash = hash_password(password_data.new_password)
    # Optional: Set flag user_must_change_password (if implemented)
    db.commit()

    # Audit-Log
    log_audit_event(
        db,
        AuditEventType.PASSWORD_CHANGE,
        f"Password for {user.username} reset by admin",
        user_id=current_user.id,
        request=request
    )
    
    return {"message": "Password successfully reset"}


@router.get("/users")
async def list_users(
    skip: int = 0,
    limit: int = 100,
    current_user: User = Depends(RequireAdmin),
    db: Session = Depends(get_db)
):
    """
    Lists all users. Admin only.
    """
    users = db.query(User).offset(skip).limit(limit).all()
    return [
        {
            "id": u.id,
            "username": u.username,
            "email": u.email,
            "role": u.role.value,
            "is_active": u.is_active,
            "failed_login_attempts": u.failed_login_attempts
        }
        for u in users
    ]


class UserUpdateRequest(BaseModel):
    role: Optional[UserRole] = None
    email: Optional[EmailStr] = None



@router.patch("/users/{user_id}")
async def update_user(
    request: Request,
    user_id: int,
    user_data: UserUpdateRequest,
    current_user: User = Depends(RequireAdmin),
    db: Session = Depends(get_db)
):
    """
    Updates user data (Role, Email). Admin only.
    """
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    changes = []

    if user_data.role:
        if user.role != user_data.role:
            log_audit_event(
                db,
                AuditEventType.PERMISSION_ESCALATION,
                f"Role for {user.username} changed: {user.role.value} -> {user_data.role.value}",
                user_id=current_user.id,
                request=request
            )
            user.role = user_data.role
            changes.append("role")

    if user_data.email:
        if user.email != user_data.email:
             # Check collision
            existing = db.query(User).filter(User.email == user_data.email).first()
            if existing and existing.id != user_id:
                raise HTTPException(status_code=400, detail="Email already in use")
            
            user.email = user_data.email
            changes.append("email")

    if not changes:
        return {"message": "No changes made", "user": {
            "id": user.id,
            "username": user.username,
            "email": user.email,
            "role": user.role.value
        }}

    db.commit()
    return {
        "message": f"User updated: {', '.join(changes)}",
        "user": {
            "id": user.id,
            "username": user.username,
            "email": user.email,
            "role": user.role.value
        }
    }


@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(
    request: Request,
    user_id: int,
    current_user: User = Depends(RequireAdmin),
    db: Session = Depends(get_db)
):
    """
    Delete a user. Admin only.

    Safety checks:
        - Cannot delete yourself
        - Cannot delete the last admin
    """
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    if user.id == current_user.id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot delete your own account"
        )

    if user.role == UserRole.ADMIN:
        admin_count = db.query(User).filter(User.role == UserRole.ADMIN).count()
        if admin_count <= 1:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Cannot delete the last admin user"
            )

    username = user.username

    # Clear user assignments before deletion to avoid FK constraint errors
    from app.models.asset import Asset
    from app.models.vulnerability import Vulnerability
    db.query(Asset).filter(Asset.assigned_user_id == user_id).update({"assigned_user_id": None})
    db.query(Vulnerability).filter(Vulnerability.assigned_user_id == user_id).update({"assigned_user_id": None})

    db.delete(user)
    db.commit()

    log_audit_event(
        db,
        AuditEventType.USER_DELETED,
        f"User deleted: {username}",
        user_id=current_user.id,
        request=request
    )

    return None
