from sqlalchemy.orm import Session, joinedload, subqueryload
from typing import Optional, List
from .models import Vitalsign, Consultation, Prescription, Labtest


# ======================== Vitalsign Repository ========================
def get_by_id(db: Session, entity_id: str) -> Optional[Vitalsign]:
    return db.query(Vitalsign).filter(Vitalsign.id == entity_id).first()


def list_all(db: Session, limit: int = 20, offset: int = 0, **filters) -> List[Vitalsign]:
    q = db.query(Vitalsign)
    if filters.get("patient_id") is not None:
        q = q.filter(Vitalsign.patient_id == filters["patient_id"])
    if filters.get("recorded_by_user_id") is not None:
        q = q.filter(Vitalsign.recorded_by_user_id == filters["recorded_by_user_id"])
    return q.order_by(Vitalsign.recorded_at.desc()).limit(limit).offset(offset).all()


def count_all(db: Session, **filters) -> int:
    q = db.query(Vitalsign.id)
    if filters.get("patient_id") is not None:
        q = q.filter(Vitalsign.patient_id == filters["patient_id"])
    if filters.get("recorded_by_user_id") is not None:
        q = q.filter(Vitalsign.recorded_by_user_id == filters["recorded_by_user_id"])
    return q.count()


def create(db: Session, data: dict) -> Vitalsign:
    obj = Vitalsign(**data)
    db.add(obj)
    db.flush()
    return obj


def update(db: Session, entity_id: str, data: dict) -> Optional[Vitalsign]:
    obj = get_by_id(db, entity_id)
    if not obj:
        return None
    for key, value in data.items():
        setattr(obj, key, value)
    db.flush()
    return obj


def delete(db: Session, entity_id: str) -> bool:
    obj = get_by_id(db, entity_id)
    if not obj:
        return False
    db.delete(obj)
    db.flush()
    return True


# ======================== Consultation Repository ========================
def get_consultation_by_id(db: Session, entity_id: str) -> Optional[Consultation]:
    return db.query(Consultation).filter(Consultation.id == entity_id).first()


def list_consultations(db: Session, limit: int = 20, offset: int = 0, **filters) -> List[Consultation]:
    q = db.query(Consultation)
    if filters.get("patient_id") is not None:
        q = q.filter(Consultation.patient_id == filters["patient_id"])
    if filters.get("doctor_id") is not None:
        q = q.filter(Consultation.doctor_id == filters["doctor_id"])
    if filters.get("appointment_id") is not None:
        q = q.filter(Consultation.appointment_id == filters["appointment_id"])
    return q.order_by(Consultation.consultation_date.desc()).limit(limit).offset(offset).all()


def count_consultations(db: Session, **filters) -> int:
    q = db.query(Consultation.id)
    if filters.get("patient_id") is not None:
        q = q.filter(Consultation.patient_id == filters["patient_id"])
    if filters.get("doctor_id") is not None:
        q = q.filter(Consultation.doctor_id == filters["doctor_id"])
    if filters.get("appointment_id") is not None:
        q = q.filter(Consultation.appointment_id == filters["appointment_id"])
    return q.count()


def create_consultation(db: Session, data: dict) -> Consultation:
    obj = Consultation(**data)
    db.add(obj)
    db.flush()
    return obj


def update_consultation(db: Session, entity_id: str, data: dict) -> Optional[Consultation]:
    obj = get_consultation_by_id(db, entity_id)
    if not obj:
        return None
    for key, value in data.items():
        setattr(obj, key, value)
    db.flush()
    return obj


def delete_consultation(db: Session, entity_id: str) -> bool:
    obj = get_consultation_by_id(db, entity_id)
    if not obj:
        return False
    db.delete(obj)
    db.flush()
    return True


def get_consultation_with_details(db: Session, entity_id: str) -> Optional[Consultation]:
    return (
        db.query(Consultation)
        .options(
            joinedload(Consultation.patient),
            joinedload(Consultation.doctor),
            joinedload(Consultation.appointment),
            subqueryload(Consultation.prescriptions),
            subqueryload(Consultation.lab_tests),
        )
        .filter(Consultation.id == entity_id)
        .first()
    )


# ======================== Prescription Repository ========================
def get_prescription_by_id(db: Session, entity_id: str) -> Optional[Prescription]:
    return db.query(Prescription).filter(Prescription.id == entity_id).first()


def list_prescriptions(db: Session, limit: int = 20, offset: int = 0, **filters) -> List[Prescription]:
    q = db.query(Prescription)
    if filters.get("patient_id") is not None:
        q = q.filter(Prescription.patient_id == filters["patient_id"])
    if filters.get("doctor_id") is not None:
        q = q.filter(Prescription.doctor_id == filters["doctor_id"])
    if filters.get("consultation_id") is not None:
        q = q.filter(Prescription.consultation_id == filters["consultation_id"])
    if filters.get("is_active") is not None:
        q = q.filter(Prescription.is_active == filters["is_active"])
    return q.order_by(Prescription.prescribed_date.desc()).limit(limit).offset(offset).all()


def count_prescriptions(db: Session, **filters) -> int:
    q = db.query(Prescription.id)
    if filters.get("patient_id") is not None:
        q = q.filter(Prescription.patient_id == filters["patient_id"])
    if filters.get("doctor_id") is not None:
        q = q.filter(Prescription.doctor_id == filters["doctor_id"])
    if filters.get("consultation_id") is not None:
        q = q.filter(Prescription.consultation_id == filters["consultation_id"])
    if filters.get("is_active") is not None:
        q = q.filter(Prescription.is_active == filters["is_active"])
    return q.count()


def create_prescription(db: Session, data: dict) -> Prescription:
    obj = Prescription(**data)
    db.add(obj)
    db.flush()
    return obj


def update_prescription(db: Session, entity_id: str, data: dict) -> Optional[Prescription]:
    obj = get_prescription_by_id(db, entity_id)
    if not obj:
        return None
    for key, value in data.items():
        setattr(obj, key, value)
    db.flush()
    return obj


def delete_prescription(db: Session, entity_id: str) -> bool:
    obj = get_prescription_by_id(db, entity_id)
    if not obj:
        return False
    db.delete(obj)
    db.flush()
    return True


# ======================== Labtest Repository ========================
def get_labtest_by_id(db: Session, entity_id: str) -> Optional[Labtest]:
    return db.query(Labtest).filter(Labtest.id == entity_id).first()


def list_labtests(db: Session, limit: int = 20, offset: int = 0, **filters) -> List[Labtest]:
    q = db.query(Labtest)
    if filters.get("patient_id") is not None:
        q = q.filter(Labtest.patient_id == filters["patient_id"])
    if filters.get("doctor_id") is not None:
        q = q.filter(Labtest.ordered_by_doctor_id == filters["doctor_id"])
    if filters.get("consultation_id") is not None:
        q = q.filter(Labtest.consultation_id == filters["consultation_id"])
    if filters.get("status") is not None:
        q = q.filter(Labtest.status == filters["status"])
    return q.order_by(Labtest.ordered_date.desc()).limit(limit).offset(offset).all()


def count_labtests(db: Session, **filters) -> int:
    q = db.query(Labtest.id)
    if filters.get("patient_id") is not None:
        q = q.filter(Labtest.patient_id == filters["patient_id"])
    if filters.get("doctor_id") is not None:
        q = q.filter(Labtest.ordered_by_doctor_id == filters["doctor_id"])
    if filters.get("consultation_id") is not None:
        q = q.filter(Labtest.consultation_id == filters["consultation_id"])
    if filters.get("status") is not None:
        q = q.filter(Labtest.status == filters["status"])
    return q.count()


def create_labtest(db: Session, data: dict) -> Labtest:
    obj = Labtest(**data)
    db.add(obj)
    db.flush()
    return obj


def update_labtest(db: Session, entity_id: str, data: dict) -> Optional[Labtest]:
    obj = get_labtest_by_id(db, entity_id)
    if not obj:
        return None
    for key, value in data.items():
        setattr(obj, key, value)
    db.flush()
    return obj


def delete_labtest(db: Session, entity_id: str) -> bool:
    obj = get_labtest_by_id(db, entity_id)
    if not obj:
        return False
    db.delete(obj)
    db.flush()
    return True