# user_service.py
import re
import uuid
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, Optional

from fastapi import HTTPException, UploadFile, status
from fastapi.responses import StreamingResponse
from sqlalchemy import distinct, func, literal, nullslast
from sqlalchemy.orm import Session, joinedload

from src.apps.auth.models.sessions_model import Session as UserSession
from src.apps.role_permission.models.role_model import Role
from src.apps.role_permission.models.user_permission_model import UserPermissions
from src.apps.role_permission.services.permission_service import (
    create_admin_role_with_all_permissions,
    seed_default_permissions,
)
from src.apps.user.models.user_model import Users
from src.apps.user.schemas.user_schemas import (
    ChangePasswordSchema,
    UserCreateSchema,
    UserFilterSchema,
    UserReturnSchema,
    UserSessionSchema,
    UserSessionsResponse,
    UserUpdateSchema,
)
from src.core.config import settings
from src.utils.constants import API_PREFIXES
from src.utils.enums import UserAccessLevel
from src.utils.helpers.auth import encrypt_password, verify_password
from src.utils.helpers.functions import (
    get_password_hash,
    get_site_settings_value,
)
from src.utils.helpers.pagination import QueryPaginator


async def get_user(db: Session, username: Optional[str] = None, email: Optional[str] = None):
    if username:
        return db.query(Users).filter(Users.username == username).first()
    if email:
        return db.query(Users).filter(Users.email == email).first()
    return None


async def create_user_service(payload: UserCreateSchema, db: Session) -> UserReturnSchema:
    # check for invalid access level
    access_levels = None
    if payload.access_level:
        access_levels = payload.access_level
    # Check if user already exists by username or email
    existing_user = db.query(Users).filter((Users.phone == payload.phone) | (Users.email == payload.email)).first()
    if existing_user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="User with this phone or email already exists",
        )
    # Check if password and confirm_password match
    if payload.password != payload.confirm_password:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Password and confirm password do not match",
        )
    # Hash the password
    hashed_password = get_password_hash(payload.password)
    # Create username from email if not provided, replace special characters with _ and add timestamp

    base_username = re.sub(r"\W+", "_", payload.email.split("@")[0])
    timestamp = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S")
    username = f"{base_username}_{timestamp}"
    # Create a new user instance
    new_user = Users(
        user_id=str(uuid.uuid4()),
        username=username,
        email=payload.email,
        password=hashed_password,
        phone=payload.phone,
        first_name=payload.first_name,
        last_name=payload.last_name,
        avatar_id=payload.avatar_id,
        # access_level=payload.access_level,
        is_active=True,
        is_verified=True,
        created_at=datetime.utcnow(),
        updated_at=datetime.utcnow(),
    )
    db.add(new_user)
    db.commit()
    db.refresh(new_user)

    # map user to role
    if access_levels:
        try:
            for idx, access_level in enumerate(access_levels):
                role = db.query(Role).filter(Role.slug == access_level).first()
                if role:
                    # Set is_primary True only for the first role
                    user_role = UserPermissions(user_id=new_user.id, role_id=role.id, is_primary=(idx == 0))
                    db.add(user_role)
                    db.commit()
        except Exception as e:
            # Log the error but don't fail registration
            print(f"Error assigning role to user: {str(e)}")


    return UserReturnSchema.model_validate(new_user)


def update_user_service(payload: UserUpdateSchema, db: Session, user_id: str) -> UserReturnSchema:
    # check user_id in database
    current_user = db.query(Users).filter(Users.user_id == user_id).first()
    if not current_user:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")

    # check for invalid access level
    access_levels = None
    if payload.access_level:
        access_levels = payload.access_level
    # Check if email is being updated and already exists for another user
    if payload.email:
        existing_user = db.query(Users).filter(Users.email == payload.email, Users.id != current_user.id).first()
        if existing_user:
            raise HTTPException(status_code=400, detail="Email already registered by another user")
    # Check if phone is being updated and already exists for another user
    if payload.phone:
        existing_user = db.query(Users).filter(Users.phone == payload.phone, Users.id != current_user.id).first()
        if existing_user:
            raise HTTPException(status_code=400, detail="Phone number already registered by another user")
    # Update user fields from payload
    for field, value in payload.dict(exclude="access_level", exclude_unset=True).items():
        setattr(current_user, field, value)

    # map user to role
    if access_levels:
        try:
            # Delete existing user role mappings
            db.query(UserPermissions).filter(UserPermissions.user_id == current_user.id).delete()
            db.commit()
            for idx, access_level in enumerate(access_levels):
                role = db.query(Role).filter(Role.slug == access_level).first()
                if role:
                    # Set is_primary True only for the first role
                    user_role = UserPermissions(user_id=current_user.id, role_id=role.id, is_primary=(idx == 0))
                    db.add(user_role)
                    db.commit()
        except Exception as e:
            # Log the error but don't fail registration
            print(f"Error assigning role to user: {str(e)}")

    db.add(current_user)
    db.commit()
    db.refresh(current_user)
    return UserReturnSchema.model_validate(current_user)


def _build_user_query(db: Session, payload: UserFilterSchema, sort_by: List[str] = None) -> tuple:
    """
    Build and return a filtered and sorted tour query.

    Args:
     db: Database session
     payload: Filter parameters
     sort_by: List of fields to sort by (prefix with - for descending order)

    Returns:
     tuple: (SQLAlchemy Query object, sort_query list if any)
    """
    query = db.query(Users).filter(Users.deleted_at == None)

    # Apply filters from payload
    if payload.full_name:
        query = query.filter(
            func.lower(Users.first_name + " " + Users.last_name).ilike(f"%{payload.full_name.lower()}%")
        )
    if payload.first_name:
        query = query.filter(func.lower(Users.first_name).ilike(f"%{payload.first_name.lower()}%"))
    if payload.last_name:
        query = query.filter(func.lower(Users.last_name).ilike(f"%{payload.last_name.lower()}%"))
    if payload.email:
        query = query.filter(func.lower(Users.email).ilike(f"%{payload.email.lower()}%"))
    if payload.phone:
        query = query.filter(Users.phone.ilike(f"%{payload.phone}%"))
    if payload.is_active is not None:
        query = query.filter(Users.is_active == payload.is_active)
    if payload.is_verified is not None:
        query = query.filter(Users.is_verified == payload.is_verified)
    if payload.access_level:
        query = query.filter(Users.has_role(payload.access_level))

    # Apply sorting if provided
    sort_query = []
    if sort_by:
        for s in sort_by:
            try:
                col = s.replace("-", "")
                if col in [
                    "first_name",
                    "last_name",
                    "email",
                    "phone",
                    "is_active",
                    "is_verified",
                    "is_foreign_visitor",
                    "is_reported",
                    "is_online",
                    "last_login",
                    "created_at",
                ]:
                    sort_order = getattr(Users, col)

                    if s.startswith("-"):
                        sort_order = sort_order.desc()

                    sort_query.append(nullslast(sort_order))
                else:
                    raise HTTPException(
                        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                        detail=f"Cannot sort with unidentified column '{s}'",
                    )
            except AttributeError:
                raise HTTPException(
                    status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                    detail=f"Cannot sort with unidentified column '{s}'",
                )

        if sort_query:
            query = query.order_by(*sort_query)

    return query, sort_query


def get_all_users_service(
    db: Session, payload: UserFilterSchema, page: int = 1, per_page: int = 10, sort_by: List[str] = None
) -> Dict:
    users = db.query(Users).all()

    query, _ = _build_user_query(db, payload, sort_by)

    offset = (page - 1) * per_page
    # Get paginated tours
    paginator = QueryPaginator(
        query=query,
        schema=UserReturnSchema,
        url="".join([str(settings.api_base_url()), API_PREFIXES.USER]),
        offset=offset,
        limit=per_page,
    )

    return paginator.paginate()


def get_user_by_user_id_service(db: Session, user_id: str) -> UserReturnSchema:
    query = db.query(Users).filter(Users.user_id == user_id)

    user = query.first()

    if not user:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")

    return UserReturnSchema.model_validate(user)


def change_password_service(db: Session, current_user: Users, payload: ChangePasswordSchema):
    userobj = db.query(Users).filter(Users.id == current_user.id).first()
    if not userobj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
    # Validate current password
    current_password = payload.current_password
    new_password = payload.new_password
    confirm_password = payload.confirm_password
    if new_password != confirm_password:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, detail="New password and confirm password do not match"
        )

    # Prevent user from reusing the same password
    if verify_password(new_password, userobj.password):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, detail="New password cannot be the same as the current password"
        )

    # Check if current_password matches the user's actual password
    if not verify_password(current_password, userobj.password):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Current password is incorrect")

    # Hash and update the new password
    hashed_password = get_password_hash(new_password)
    userobj.password = hashed_password
    userobj.updated_at = datetime.utcnow()
    db.commit()
    db.refresh(userobj)
    return {"detail": "Password changed successfully"}


async def update_user_profile(user: Users, payload: UserUpdateSchema, db: Session) -> UserReturnSchema:
    return update_user_service(payload, db, user.user_id)


def get_user_sessions_with_limit(db: Session, user_id: int) -> UserSessionsResponse:
    # 1. Fetch session_number setting from the value field
    max_sessions = 5
    max_sessions = get_site_settings_value(db, "session_number", default=max_sessions)

    # 2. Get recent sessions
    sessions = (
        db.query(UserSession)
        .filter(UserSession.user_id == user_id)
        .order_by(UserSession.created_at.desc())
        .limit(max_sessions)
        .all()
    )

    return UserSessionsResponse(
        max_allowed_sessions=max_sessions,
        active_count=len(sessions),
        active_sessions=[UserSessionSchema.from_orm_with_location(s) for s in sessions],
    )


def delete_user_service(db: Session, user_id: str):
    user = db.query(Users).filter(Users.user_id == user_id).first()
    if not user:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")

    # Soft delete the user
    user.deleted_at = datetime.utcnow()
    db.commit()
    db.refresh(user)

    return {"message": "User deleted successfully"}


def user_status_change_service(db: Session, user_id: str, is_active: bool):
    user = db.query(Users).filter(Users.user_id == user_id).first()
    if not user:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
    user.is_active = is_active
    db.commit()
    db.refresh(user)
    return {"message": f"User status changed to {'active' if is_active else 'inactive'} successfully"}



