"""User repository for database operations on users."""
from datetime import UTC, datetime
from typing import Optional

from bson import ObjectId
from pymongo import ReturnDocument

from app.db import Database
from app.models import graph_preset, prescription
from app.models.user import User
from app.repositories import device_repository, diagnosis_repository, graph_preset_repository, hospital_repository, operation_repository, prescription_repository, test_repository, vaccine_repository

COL = "users"

# Collections that store user-owned data and should be removed when a user is deleted.
_USER_OWNED_COLLECTION_FILTERS: tuple[tuple[str, str], ...] = (
    (graph_preset_repository.COL, "patientId"),
    (test_repository.COL, "patientId"),
    (diagnosis_repository.COL, "patientId"),
    (hospital_repository.COL, "patientId"),
    (operation_repository.COL, "patientId"),
    (prescription_repository.COL, "patientId"),
    (vaccine_repository.COL, "patientId"),
    (device_repository.COL_CONNECTIONS, "userId"),
    (device_repository.COL_READINGS, "userId"),
)


def create(user: User) -> User:
    """Create a new user in the database."""
    now = datetime.now(UTC)
    user_to_insert = user.model_copy(update={"created_at": now, "updated_at": now}).to_bson()
    res = Database.get_db().get_collection(COL).insert_one(user_to_insert)
    return User.from_bson({**user_to_insert, "_id": res.inserted_id})


def find_by_id(user_id: str) -> Optional[User]:
    """Find a user by their ID."""
    if not ObjectId.is_valid(user_id):
        return None
    doc = Database.get_db().get_collection(COL).find_one({"_id": ObjectId(user_id)})
    return User.from_bson(doc) if doc else None


def find_by_email(email: str) -> Optional[User]:
    """Find a user by their email address."""
    doc = Database.get_db().get_collection(COL).find_one({"email": email})
    return User.from_bson(doc) if doc else None


def find_by_username(username: str) -> Optional[User]:
    """Find a user by their username."""
    doc = Database.get_db().get_collection(COL).find_one({"username": username})
    return User.from_bson(doc) if doc else None


def find_by_role(role: str) -> list[User]:
    """Find all users that have a specific role."""
    docs = Database.get_db().get_collection(COL).find({"roles": role})
    return [User.from_bson(doc) for doc in docs]


def find_by_ids(user_ids: list[str]) -> list[User]:
    """Find multiple users by their IDs in a single query."""
    valid_ids = [ObjectId(uid) for uid in user_ids if ObjectId.is_valid(uid)]
    if not valid_ids:
        return []
    docs = Database.get_db().get_collection(COL).find({"_id": {"$in": valid_ids}})
    return [User.from_bson(doc) for doc in docs]


def update(user_id: str, updates: dict) -> Optional[User]:
    """Update a user's information."""
    if not ObjectId.is_valid(user_id):
        return None
    db = Database.get_db()
    existing = db.get_collection(COL).find_one({"_id": ObjectId(user_id)})
    if not existing:
        return None

    # Merge existing document with updates, validate via model,
    # and ensure updated_at is refreshed.
    merged = {**existing, **updates}
    candidate = User.from_bson(merged).model_copy(update={"updated_at": datetime.now(UTC)})
    to_set = candidate.to_bson()
    to_set.pop("_id", None)

    doc = db.get_collection(COL).find_one_and_update(
        {"_id": ObjectId(user_id)},
        {"$set": to_set},
        return_document=ReturnDocument.AFTER,
    )
    return User.from_bson(doc) if doc else None


def delete(user_id: str) -> bool:
    """Delete a user from the database. Returns True if deleted."""
    if not ObjectId.is_valid(user_id):
        return False

    user_obj_id = ObjectId(user_id)
    db = Database.get_db()
    existing = db.get_collection(COL).find_one({"_id": user_obj_id}, {"_id": 1})
    if not existing:
        return False

    _delete_user_owned_data(user_obj_id)
    result = db.get_collection(COL).delete_one({"_id": user_obj_id})
    return result.deleted_count > 0


def _delete_user_owned_data(user_obj_id: ObjectId) -> None:
    """Delete all known user-owned documents from related collections."""
    db = Database.get_db()
    for collection_name, owner_field in _USER_OWNED_COLLECTION_FILTERS:
        db.get_collection(collection_name).delete_many({owner_field: user_obj_id})