from fastapi import APIRouter, Depends, HTTPException, status, Query, Request
from sqlalchemy.orm import Session, joinedload
from sqlalchemy import or_
from typing import Optional
import bcrypt
from ...shared.config.database import get_db
from ...security.middleware.auth import get_current_user, authorize_roles
from ...security.middleware.step_up_auth import require_step_up_auth, require_admin_step_up_auth
from ..models.user import User
from ..models.role import Role
from ...bookings.models.booking import Booking, BookingStatus
from ...shared.utils.role_helpers import can_manage_users
from ...shared.utils.response_helpers import success_response
from ...analytics.services.audit_service import audit_service
from ...shared.config.logging_config import get_logger
from ..schemas.user import CreateUserRequest, UpdateUserRequest

logger = get_logger(__name__)
router = APIRouter(prefix='/users', tags=['users'])

@router.get('/')
async def get_users(search: Optional[str]=Query(None), role: Optional[str]=Query(None), status_filter: Optional[str]=Query(None, alias='status'), page: int=Query(1, ge=1), limit: int=Query(10, ge=1, le=100), current_user: User=Depends(authorize_roles('admin', 'staff')), db: Session=Depends(get_db)):
    try:
        query = db.query(User)
        if search:
            query = query.filter(or_(User.full_name.like(f'%{search}%'), User.email.like(f'%{search}%'), User.phone.like(f'%{search}%')))
        if role:
            # Query role by name instead of hardcoded IDs for flexibility
            role_obj = db.query(Role).filter(Role.name == role).first()
            if role_obj:
                query = query.filter(User.role_id == role_obj.id)
        if status_filter:
            is_active = status_filter == 'active'
            query = query.filter(User.is_active == is_active)
        total = query.count()
        offset = (page - 1) * limit
        users = query.order_by(User.created_at.desc()).offset(offset).limit(limit).all()
        result = []
        for user in users:
            user_dict = {'id': user.id, 'email': user.email, 'full_name': user.full_name, 'phone': user.phone, 'phone_number': user.phone, 'address': user.address, 'avatar': user.avatar, 'currency': getattr(user, 'currency', 'VND'), 'is_active': user.is_active, 'status': 'active' if user.is_active else 'inactive', 'role_id': user.role_id, 'role': user.role.name if user.role else 'customer', 'created_at': user.created_at.isoformat() if user.created_at else None, 'updated_at': user.updated_at.isoformat() if user.updated_at else None}
            result.append(user_dict)
        return success_response(data={'users': result, 'pagination': {'total': total, 'page': page, 'limit': limit, 'totalPages': (total + limit - 1) // limit}})
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/{id}', dependencies=[Depends(authorize_roles('admin'))])
async def get_user_by_id(id: int, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
    try:
        user = db.query(User).filter(User.id == id).first()
        if not user:
            raise HTTPException(status_code=404, detail='User not found')
        bookings = db.query(Booking).filter(Booking.user_id == id).order_by(Booking.created_at.desc()).limit(5).all()
        user_dict = {'id': user.id, 'email': user.email, 'full_name': user.full_name, 'phone': user.phone, 'phone_number': user.phone, 'address': user.address, 'avatar': user.avatar, 'currency': getattr(user, 'currency', 'VND'), 'is_active': user.is_active, 'status': 'active' if user.is_active else 'inactive', 'role_id': user.role_id, 'role': user.role.name if user.role else 'customer', 'created_at': user.created_at.isoformat() if user.created_at else None, 'updated_at': user.updated_at.isoformat() if user.updated_at else None, 'bookings': [{'id': b.id, 'booking_number': b.booking_number, 'status': b.status.value if isinstance(b.status, BookingStatus) else b.status, 'created_at': b.created_at.isoformat() if b.created_at else None} for b in bookings]}
        return success_response(data={'user': user_dict})
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.post('/', dependencies=[Depends(authorize_roles('admin')), Depends(require_admin_step_up_auth("user creation"))])
async def create_user(
    request: Request,
    user_data: CreateUserRequest, 
    current_user: User=Depends(authorize_roles('admin')), 
    db: Session=Depends(get_db)
):
    """Create a user with validated input using Pydantic schema."""
    client_ip = request.client.host if request.client else None
    user_agent = request.headers.get('User-Agent')
    request_id = getattr(request.state, 'request_id', None)
    
    try:
        email = user_data.email
        password = user_data.password
        full_name = user_data.full_name
        phone_number = user_data.phone_number
        
        # Get customer role for default
        customer_role = db.query(Role).filter(Role.name == 'customer').first()
        if not customer_role:
            raise HTTPException(status_code=500, detail='Customer role not found')
        
        # Handle role - accept either role_id or role name
        role_id = None
        if user_data.role_id is not None:
            role_id = user_data.role_id
        elif user_data.role is not None:
            # Convert role name to role_id
            role_by_name = db.query(Role).filter(Role.name == user_data.role).first()
            if not role_by_name:
                raise HTTPException(status_code=400, detail=f'Invalid role name: {user_data.role}')
            role_id = role_by_name.id
        else:
            # Default to customer role
            role_id = customer_role.id
        
        # Validate that the role exists
        role = db.query(Role).filter(Role.id == role_id).first()
        if not role:
            raise HTTPException(status_code=400, detail='Invalid role specified')
        
        existing = db.query(User).filter(User.email == email).first()
        if existing:
            raise HTTPException(status_code=400, detail='Email already exists')
        password_bytes = password.encode('utf-8')
        salt = bcrypt.gensalt()
        hashed_password = bcrypt.hashpw(password_bytes, salt).decode('utf-8')
        from datetime import datetime
        user = User(
            email=email, 
            password=hashed_password, 
            full_name=full_name, 
            phone=phone_number, 
            role_id=role_id, 
            is_active=True, 
            email_verified=False,  # Admin-created users need email verification
            password_changed_at=datetime.utcnow()  # Set initial password change timestamp
        )
        db.add(user)
        db.commit()
        db.refresh(user)
        
        # Log admin action - user creation
        await audit_service.log_action(
            db=db,
            action='admin_user_created',
            resource_type='user',
            user_id=current_user.id,
            resource_id=user.id,
            ip_address=client_ip,
            user_agent=user_agent,
            request_id=request_id,
            details={
                'created_user_email': user.email,
                'created_user_name': user.full_name,
                'role_id': user.role_id,
                'is_active': user.is_active
            },
            status='success'
        )
        
        user_dict = {'id': user.id, 'email': user.email, 'full_name': user.full_name, 'phone': user.phone, 'phone_number': user.phone, 'currency': getattr(user, 'currency', 'VND'), 'role_id': user.role_id, 'is_active': user.is_active}
        return success_response(data={'user': user_dict}, message='User created successfully')
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=str(e))

@router.put('/{id}', dependencies=[Depends(authorize_roles('admin')), Depends(require_admin_step_up_auth("user management"))])
async def update_user(
    id: int, 
    request: Request, 
    user_data: UpdateUserRequest, 
    current_user: User=Depends(get_current_user), 
    db: Session=Depends(get_db)
):
    """Update a user with validated input using Pydantic schema."""
    client_ip = request.client.host if request.client else None
    user_agent = request.headers.get('User-Agent')
    request_id = getattr(request.state, 'request_id', None)
    
    try:
        if not can_manage_users(current_user, db) and current_user.id != id:
            raise HTTPException(status_code=403, detail='Forbidden')
        
        # Step-up is no longer enforced for admins via accountant security.
        # If a separate admin step-up is desired, wire it here instead.
        
        user = db.query(User).options(joinedload(User.role)).filter(User.id == id).first()
        if not user:
            raise HTTPException(status_code=404, detail='User not found')
        
        # Track changes for audit logging
        changes = {}
        old_role_id = user.role_id
        old_role_name = user.role.name if user.role else None
        old_is_active = user.is_active
        old_email_verified = getattr(user, 'email_verified', True)
        
        # Check email uniqueness if being updated
        if user_data.email and user_data.email != user.email:
            existing = db.query(User).filter(User.email == user_data.email).first()
            if existing:
                raise HTTPException(status_code=400, detail='Email already exists')
        
        # Update fields if provided
        if user_data.full_name is not None:
            user.full_name = user_data.full_name
        if user_data.email is not None and can_manage_users(current_user, db):
            if user_data.email != user.email:
                changes['email'] = {'old': user.email, 'new': user_data.email}
            user.email = user_data.email
        if user_data.phone_number is not None:
            user.phone = user_data.phone_number
        
        # Handle role update - accept either role_id or role name
        role_id_to_set = None
        if user_data.role_id is not None:
            role_id_to_set = user_data.role_id
        elif user_data.role is not None:
            # Convert role name to role_id
            role_by_name = db.query(Role).filter(Role.name == user_data.role).first()
            if not role_by_name:
                raise HTTPException(status_code=400, detail=f'Invalid role name: {user_data.role}')
            role_id_to_set = role_by_name.id
        
        if role_id_to_set is not None and can_manage_users(current_user, db):
            # SECURITY: Prevent admin from changing their own role
            if current_user.id == id:
                raise HTTPException(
                    status_code=400,
                    detail='You cannot change your own role. Please ask another admin to do it.'
                )
            new_role = db.query(Role).filter(Role.id == role_id_to_set).first()
            if not new_role:
                raise HTTPException(status_code=400, detail='Invalid role ID specified')
            new_role_name = new_role.name
            if role_id_to_set != old_role_id:
                changes['role'] = {'old': old_role_name, 'new': new_role_name, 'old_id': old_role_id, 'new_id': role_id_to_set}
            user.role_id = role_id_to_set
        if user_data.is_active is not None and can_manage_users(current_user, db):
            if user_data.is_active != old_is_active:
                changes['is_active'] = {'old': old_is_active, 'new': user_data.is_active}
            user.is_active = user_data.is_active
        db.commit()
        db.refresh(user)
        
        # SECURITY: Log sensitive user changes for audit trail
        if changes and can_manage_users(current_user, db):
            try:
                audit_details = {
                    'updated_user_id': user.id,
                    'updated_user_email': user.email,
                    'changes': changes
                }
                
                # Log role changes separately as high-priority audit events
                if 'role' in changes:
                    await audit_service.log_action(
                        db=db,
                        action='user_role_changed',
                        resource_type='user',
                        user_id=current_user.id,
                        resource_id=user.id,
                        ip_address=client_ip,
                        user_agent=user_agent,
                        request_id=request_id,
                        details=audit_details,
                        status='success'
                    )
                
                # Log account status changes (activation/deactivation)
                if 'is_active' in changes:
                    action_name = 'user_deactivated' if not user_data.is_active else 'user_activated'
                    await audit_service.log_action(
                        db=db,
                        action=action_name,
                        resource_type='user',
                        user_id=current_user.id,
                        resource_id=user.id,
                        ip_address=client_ip,
                        user_agent=user_agent,
                        request_id=request_id,
                        details=audit_details,
                        status='success'
                    )
                
                # Log other significant changes
                if 'email' in changes:
                    await audit_service.log_action(
                        db=db,
                        action='user_email_changed',
                        resource_type='user',
                        user_id=current_user.id,
                        resource_id=user.id,
                        ip_address=client_ip,
                        user_agent=user_agent,
                        request_id=request_id,
                        details=audit_details,
                        status='success'
                    )
            except Exception as e:
                logger.warning(f'Failed to log user update audit: {e}')
        
        user_dict = {'id': user.id, 'email': user.email, 'full_name': user.full_name, 'phone': user.phone, 'phone_number': user.phone, 'currency': getattr(user, 'currency', 'VND'), 'role_id': user.role_id, 'is_active': user.is_active}
        return success_response(data={'user': user_dict}, message='User updated successfully')
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=str(e))

@router.delete('/{id}', dependencies=[Depends(authorize_roles('admin')), Depends(require_step_up_auth("user deletion"))])
async def delete_user(id: int, request: Request, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
    client_ip = request.client.host if request.client else None
    user_agent = request.headers.get('User-Agent')
    request_id = getattr(request.state, 'request_id', None)
    
    try:
        user = db.query(User).options(joinedload(User.role)).filter(User.id == id).first()
        if not user:
            raise HTTPException(status_code=404, detail='User not found')
        
        # SECURITY: Prevent admin from deleting themselves
        if current_user.id == id:
            raise HTTPException(
                status_code=400,
                detail='You cannot delete your own account. Please ask another admin to do it.'
            )
        
        # Capture user info before deletion for audit
        deleted_user_info = {
            'user_id': user.id,
            'email': user.email,
            'full_name': user.full_name,
            'role': user.role.name if user.role else None,
            'role_id': user.role_id,
            'is_active': user.is_active
        }
        
        active_bookings = db.query(Booking).filter(Booking.user_id == id, Booking.status.in_([BookingStatus.pending, BookingStatus.confirmed, BookingStatus.checked_in])).count()
        if active_bookings > 0:
            raise HTTPException(status_code=400, detail='Cannot delete user with active bookings')
        
        # Log user deletion BEFORE deletion (so we can reference the user)
        try:
            await audit_service.log_action(
                db=db,
                action='user_deleted',
                resource_type='user',
                user_id=current_user.id,
                resource_id=id,
                ip_address=client_ip,
                user_agent=user_agent,
                request_id=request_id,
                details=deleted_user_info,
                status='success'
            )
        except Exception as e:
            logger.warning(f'Failed to log user deletion audit: {e}')
        
        # Handle foreign key constraints: Anonymize audit logs before deletion
        # This prevents foreign key constraint errors while preserving audit trail
        try:
            from ...analytics.models.audit_log import AuditLog
            audit_logs = db.query(AuditLog).filter(AuditLog.user_id == id).all()
            for log in audit_logs:
                # Set user_id to None to break foreign key constraint
                # This anonymizes the logs while keeping them for security monitoring
                log.user_id = None
            if audit_logs:
                db.flush()  # Flush changes before deleting user
                logger.info(f'Anonymized {len(audit_logs)} audit logs for user {id} before deletion')
        except Exception as e:
            logger.warning(f'Could not anonymize audit logs for user {id}: {str(e)}')
            # Continue with deletion attempt - if it fails, we'll catch the constraint error
        
        # Delete the user
        try:
            db.delete(user)
            db.commit()
        except Exception as delete_error:
            db.rollback()
            error_msg = str(delete_error)
            # Check for foreign key constraint errors
            if 'foreign key' in error_msg.lower() or 'constraint' in error_msg.lower() or '1451' in error_msg:
                logger.error(f'Foreign key constraint error when deleting user {id}: {error_msg}')
                raise HTTPException(
                    status_code=400,
                    detail='Cannot delete user: User has associated records (bookings, payments, audit logs, etc.) that prevent deletion. Please deactivate the user instead.'
                )
            else:
                logger.error(f'Error deleting user {id}: {error_msg}', exc_info=True)
                raise HTTPException(
                    status_code=500,
                    detail=f'Error deleting user: {error_msg}'
                )
        
        return success_response(message='User deleted successfully')
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        logger.error(f'Unexpected error deleting user {id}: {str(e)}', exc_info=True)
        raise HTTPException(status_code=500, detail=f'Error deleting user: {str(e)}')