from time import time

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session

from sqlalchemy.orm import selectinload

from app.programme.models_programme import ProgrammeModule, ProgrammeSession, ProgrammeWeek
from app.programme.models_programme import ProgrammeLevel  # noqa: F401 (import for selectinload typing)
from app.services.admin_service import calculate_member_points_bulk

from app.database import get_db
from app.dependencies import require_admin_token
from app.models import User, UserRole
from app.schemas import (
    AdminMemberAccountUpdateRequest,
    AdminMemberGoogleDriveUpdateRequest,
    AdminMemberPersonalInfoUpdateRequest,
    MemberGoogleDriveEntryOut,
    MemberGoogleDriveListResponse,
    MemberInactiveFollowUpListResponse,
    MemberInactiveFollowUpOut,
    MemberInactiveFollowUpUserIdRequest,
    Message,
    UpdateUserProgrammeRequest,
    UpdateUserRequest,
    UserCreate,
    UserIdRequest,
    AdminUsersListResponse,
    UserPublic,
)
from app.security import hash_password
from app.services.admin_member_service import (
    _load_member_user,
    assign_user_programme,
    list_members_google_drive_links,
    serialize_admin_member,
    serialize_admin_member_light,
    serialize_admin_member_minimal,
    serialize_member_google_drive_entry,
    update_member_google_drive_link,
    update_member_personal_info,
)
from app.services.member_inactive_followup_service import (
    DEFAULT_INACTIVE_DAYS,
    list_member_inactive_followups,
    mark_member_inactive_follow_up_sent,
    sync_member_inactive_followups,
)

router = APIRouter(prefix="/users", tags=["admin-users"])

_LIST_USERS_CACHE: dict[str, tuple[float, AdminUsersListResponse]] = {}


def invalidate_list_users_cache() -> None:
    _LIST_USERS_CACHE.clear()


def _fetch_users(db: Session, skip: int, limit: int) -> list[User]:
    return (
        db.query(User)
        .order_by(User.created_at.desc(), User.id.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )


@router.get(
    "/list-users",
    response_model=AdminUsersListResponse,
    summary="List users (paginated)",
)
@router.get(
    "/get-all-users",
    response_model=AdminUsersListResponse,
    summary="Get all users (paginated, same as list-users)",
)
def list_users(
    db: Session = Depends(get_db),
    _: User = Depends(require_admin_token),
    skip: int = Query(0, ge=0),
    limit: int = Query(50, ge=1, le=500),
    role: UserRole | None = Query(None, description="Filter by role (e.g. user for members only)"),
    cache_seconds: int = Query(10, ge=0, le=300),
):
    cache_key = f"{role or 'all'}:{skip}:{limit}"
    if cache_seconds > 0:
        cached = _LIST_USERS_CACHE.get(cache_key)
        if cached:
            expires_at, payload = cached
            if time() < expires_at:
                return payload

    base_query = db.query(User)
    if role is not None:
        base_query = base_query.filter(User.role == role)
    total = base_query.count()

    users = (
        base_query.options(
            selectinload(User.programme_session)
            .selectinload(ProgrammeSession.week)
            .selectinload(ProgrammeWeek.module)
            .selectinload(ProgrammeModule.level),
            selectinload(User.programme_assessment),
        )
        .order_by(User.created_at.desc(), User.id.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )
    from app.services.user_track_service import _load_programme_levels_ordered

    ids = [u.id for u in users]
    points_by_user = calculate_member_points_bulk(db, ids)
    programme_levels = _load_programme_levels_ordered(db)
    payload = AdminUsersListResponse(
        total=total,
        users=[
            serialize_admin_member_light(
                u, db=db, programme_levels=programme_levels, points=points_by_user.get(u.id)
            )
            for u in users
        ],
    )
    if cache_seconds > 0:
        _LIST_USERS_CACHE[cache_key] = (time() + cache_seconds, payload)
    return payload


@router.get(
    "/get-all-users-light",
    response_model=list[UserPublic],
    summary="Get users (very fast: no programme/progress/points)",
)
def list_users_light(
    db: Session = Depends(get_db),
    _: User = Depends(require_admin_token),
    skip: int = Query(0, ge=0),
    limit: int = Query(50, ge=1, le=500),
    cache_seconds: int = Query(30, ge=0, le=300),
):
    cache_key = f"light:{skip}:{limit}"
    if cache_seconds > 0:
        cached = _LIST_USERS_CACHE.get(cache_key)
        if cached:
            expires_at, payload = cached
            if time() < expires_at:
                return payload

    users = (
        db.query(User)
        .order_by(User.created_at.desc(), User.id.desc())
        .offset(skip)
        .limit(limit)
        .all()
    )
    payload = [serialize_admin_member_minimal(u) for u in users]
    if cache_seconds > 0:
        _LIST_USERS_CACHE[cache_key] = (time() + cache_seconds, payload)
    return payload


@router.get(
    "/get-user",
    response_model=UserPublic,
    summary="Get one user by id (query: user_id)",
)
@router.get(
    "/get-user-data",
    response_model=UserPublic,
    summary="Get one user by id (query: user_id, alias)",
)
@router.get(
    "/member-personal-info",
    response_model=UserPublic,
    summary="Get member personal info including phone_number (query: user_id)",
)
def get_user(
    user_id: int = Query(..., ge=1, description="User primary key"),
    db: Session = Depends(get_db),
    _: User = Depends(require_admin_token),
):
    u = db.get(User, user_id)
    if not u:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
    return serialize_admin_member(u, db)


@router.post("/create-user", response_model=UserPublic, status_code=status.HTTP_201_CREATED)
def create_user(body: UserCreate, db: Session = Depends(get_db), _: User = Depends(require_admin_token)):
    if db.query(User).filter(User.email == body.email).first():
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Email already registered")
    u = User(
        email=str(body.email),
        full_name=body.full_name,
        hashed_password=hash_password(body.password),
        role=UserRole.admin if body.role.value == "admin" else UserRole.user,
        is_active=body.is_active,
    )
    db.add(u)
    db.commit()
    db.refresh(u)
    return serialize_admin_member(u, db)


@router.post("/update-user", response_model=UserPublic)
def update_user(
    body: UpdateUserRequest,
    db: Session = Depends(get_db),
    _: User = Depends(require_admin_token),
):
    u = db.get(User, body.user_id)
    if not u:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
    if body.email is not None and body.email != u.email:
        if db.query(User).filter(User.email == body.email).first():
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Email in use")
        u.email = str(body.email)
    if body.full_name is not None:
        u.full_name = body.full_name
    if body.is_active is not None:
        u.is_active = body.is_active
    if body.password:
        u.hashed_password = hash_password(body.password)
    if body.role is not None:
        u.role = UserRole.admin if body.role.value == "admin" else UserRole.user
    db.commit()
    db.refresh(u)
    return serialize_admin_member(u, db)


@router.post(
    "/update-member-account",
    response_model=UserPublic,
    summary="Admin update member email and/or reset password",
)
def update_member_account(
    body: AdminMemberAccountUpdateRequest,
    db: Session = Depends(get_db),
    _: User = Depends(require_admin_token),
):
    if body.email is None and body.password is None:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Send email or password to update",
        )

    member = db.get(User, body.user_id)
    if not member:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Member not found")
    if member.role != UserRole.user:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Target account is not a member")

    if body.email is not None:
        new_email = str(body.email)
        if new_email != member.email:
            existing = db.query(User).filter(User.email == new_email, User.id != member.id).first()
            if existing:
                raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Email in use")
            member.email = new_email

    if body.password is not None:
        member.hashed_password = hash_password(body.password)

    db.commit()
    db.refresh(member)
    return serialize_admin_member(member, db)


@router.post(
    "/update-member-personal-info",
    response_model=UserPublic,
    summary="Update member personal info (name, phone_number, level, module, week, Google Drive link)",
)
def update_member_personal_info_route(
    body: AdminMemberPersonalInfoUpdateRequest,
    db: Session = Depends(get_db),
    _: User = Depends(require_admin_token),
):
    user = update_member_personal_info(db, body)
    return serialize_admin_member(user, db)


@router.get(
    "/list-member-google-drive-links",
    response_model=MemberGoogleDriveListResponse,
    summary="List members with Google Drive links for WhatsApp video submission management",
)
def list_member_google_drive_links_route(
    db: Session = Depends(get_db),
    _: User = Depends(require_admin_token),
    skip: int = Query(0, ge=0),
    limit: int = Query(50, ge=1, le=500),
    search: str | None = Query(None, description="Filter by member name or email"),
    link_status: str = Query(
        "all",
        description="all | with_link | without_link",
    ),
):
    members, total = list_members_google_drive_links(
        db,
        skip=skip,
        limit=limit,
        search=search,
        link_status=link_status,
    )
    serialized = [
        MemberGoogleDriveEntryOut.model_validate(serialize_member_google_drive_entry(m, db))
        for m in members
    ]
    return MemberGoogleDriveListResponse(total=total, members=serialized)


@router.get(
    "/member-google-drive-link",
    response_model=MemberGoogleDriveEntryOut,
    summary="Get one member's Google Drive link and programme week context",
)
def get_member_google_drive_link_route(
    user_id: int = Query(..., ge=1),
    db: Session = Depends(get_db),
    _: User = Depends(require_admin_token),
):
    user = _load_member_user(db, user_id)
    return MemberGoogleDriveEntryOut.model_validate(serialize_member_google_drive_entry(user, db))


@router.post(
    "/update-member-google-drive-link",
    response_model=MemberGoogleDriveEntryOut,
    summary="Attach or update a member's personalized Google Drive link",
)
def update_member_google_drive_link_route(
    body: AdminMemberGoogleDriveUpdateRequest,
    db: Session = Depends(get_db),
    _: User = Depends(require_admin_token),
):
    user = update_member_google_drive_link(db, body.user_id, body.google_drive_link)
    return MemberGoogleDriveEntryOut.model_validate(serialize_member_google_drive_entry(user, db))


@router.post(
    "/update-user-programme",
    response_model=UserPublic,
    summary="Assign programme level, module, week, and session for a member",
)
def update_user_programme_route(
    body: UpdateUserProgrammeRequest,
    db: Session = Depends(get_db),
    _: User = Depends(require_admin_token),
):
    user = assign_user_programme(db, body)
    return serialize_admin_member(user, db)


@router.get(
    "/list-member-inactive-followups",
    response_model=MemberInactiveFollowUpListResponse,
    summary="List members inactive for N days (Need Follow-Up tab). Syncs table on each call.",
)
def list_member_inactive_followups_route(
    db: Session = Depends(get_db),
    _: User = Depends(require_admin_token),
    inactive_days: int = Query(DEFAULT_INACTIVE_DAYS, ge=1, le=365, description="Days without activity"),
    skip: int = Query(0, ge=0),
    limit: int = Query(50, ge=1, le=500),
    sync: bool = Query(True, description="Rebuild inactive follow-up table before listing"),
):
    members, total, days = list_member_inactive_followups(
        db, inactive_days=inactive_days, skip=skip, limit=limit, sync=sync
    )
    return MemberInactiveFollowUpListResponse(
        total=total,
        inactive_days=days,
        members=[MemberInactiveFollowUpOut.model_validate(m) for m in members],
    )


@router.post(
    "/sync-member-inactive-followups",
    response_model=Message,
    summary="Rebuild member_inactive_followups table from current activity",
)
def sync_member_inactive_followups_route(
    db: Session = Depends(get_db),
    _: User = Depends(require_admin_token),
    inactive_days: int = Query(DEFAULT_INACTIVE_DAYS, ge=1, le=365),
):
    count = sync_member_inactive_followups(db, inactive_days=inactive_days)
    return Message(message=f"Synced {count} inactive member(s) (no activity for {inactive_days}+ days)")


@router.post(
    "/mark-member-inactive-follow-up-sent",
    response_model=Message,
    summary="Mark that admin sent follow-up to an inactive member",
)
def mark_member_inactive_follow_up_sent_route(
    body: MemberInactiveFollowUpUserIdRequest,
    db: Session = Depends(get_db),
    _: User = Depends(require_admin_token),
):
    row = mark_member_inactive_follow_up_sent(db, body.user_id)
    if not row:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Inactive follow-up record not found")
    return Message(message="Follow-up marked as sent")


@router.post("/delete-user", response_model=Message)
def delete_user(body: UserIdRequest, db: Session = Depends(get_db), admin: User = Depends(require_admin_token)):
    if body.user_id == admin.id:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot delete yourself")
    u = db.get(User, body.user_id)
    if not u:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
    db.delete(u)
    db.commit()
    return Message(message="User deleted")
