"""Hospital visit repository for database operations on hospital visits."""
import re
from datetime import UTC, datetime
from typing import Optional

from bson import ObjectId

from app.db import Database
from app.models.hospital_visit import HospitalVisit

COL = "hospital_visits"

# Honorifics that may or may not appear in front of a doctor's name. We strip
# these on both sides before matching so that a practitioner whose dispName is
# "Prof. Dr. Emily Chen" still matches a visit recorded under "Dr. Emily Chen"
# or just "Emily Chen". Order matters: longer prefixes first so "Assoc. Prof."
# is consumed before the inner "Prof.".
_TITLE_PATTERN = re.compile(
    r"^\s*(?:assoc\.?\s*prof\.?|asst\.?\s*prof\.?|prof\.?|dr\.?|md|do|phd)\s+",
    re.IGNORECASE,
)


def _strip_titles(name: str) -> str:
    """Remove leading honorifics like 'Prof. Dr.' from a doctor name."""
    prev = None
    out = name.strip()
    while out != prev:
        prev = out
        out = _TITLE_PATTERN.sub("", out).strip()
    return out


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


def find_by_id(hospital_visit_id: str) -> Optional[HospitalVisit]:
    """Find a hospital visit by its ID."""
    if not ObjectId.is_valid(hospital_visit_id):
        return None
    doc = Database.get_db().get_collection(COL).find_one({"_id": ObjectId(hospital_visit_id)})
    return HospitalVisit.from_bson(doc) if doc else None


def find_by_patient_id(patient_id: str) -> list[HospitalVisit]:
    """Find all hospital visits for a patient."""
    if not ObjectId.is_valid(patient_id):
        raise ValueError("Invalid patient ID")
    docs = Database.get_db().get_collection(COL).find({"patientId": ObjectId(patient_id)})
    return [HospitalVisit.from_bson(doc) for doc in docs]


def find_by_patient_ids(patient_ids: list[str], start_date=None, end_date=None, doctor_name: Optional[str] = None) -> list[HospitalVisit]:
    """Find hospital visits for multiple patients in a single query."""
    valid_ids = [ObjectId(pid) for pid in patient_ids if ObjectId.is_valid(pid)]
    if not valid_ids:
        return []
    query: dict = {"patientId": {"$in": valid_ids}}
    if doctor_name:
        # Match on the title-stripped core of the name so that
        # "Prof. Dr. Emily Chen" finds visits stored as "Dr. Emily Chen".
        # We anchor with optional leading honorifics in the regex to keep
        # this an indexable-ish prefix match rather than a substring match
        # that could collide with unrelated doctors sharing a surname.
        core = _strip_titles(doctor_name)
        if core:
            query["doctorName"] = {
                "$regex": f"^(?:(?:assoc\\.?\\s*prof\\.?|asst\\.?\\s*prof\\.?|prof\\.?|dr\\.?|md|do|phd)\\s+)*{re.escape(core)}$",
                "$options": "i",
            }
    if start_date or end_date:
        date_filter: dict = {}
        if start_date:
            date_filter["$gte"] = start_date
        if end_date:
            date_filter["$lte"] = end_date
        query["visitDate"] = date_filter
    docs = Database.get_db().get_collection(COL).find(query)
    return [HospitalVisit.from_bson(doc) for doc in docs]


def update(hospital_visit_id: str, updates: dict) -> Optional[HospitalVisit]:
    """Update a hospital visit record."""
    if not ObjectId.is_valid(hospital_visit_id):
        return None
    db = Database.get_db()
    existing = db.get_collection(COL).find_one({"_id": ObjectId(hospital_visit_id)})
    if not existing:
        return None

    # Merge existing document with updates, validate via model,
    # and ensure updated_at is refreshed.
    merged = {**existing, **updates}
    candidate = HospitalVisit.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(hospital_visit_id)},
        {"$set": to_set},
    )
    return HospitalVisit.from_bson(doc) if doc else None


def delete(hospital_visit_id: str) -> bool:
    """Delete a hospital visit record."""
    if not ObjectId.is_valid(hospital_visit_id):
        return False
    res = Database.get_db().get_collection(COL).delete_one({"_id": ObjectId(hospital_visit_id)})
    return res.deleted_count > 0
