from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from fastapi import HTTPException, status
from . import repository
from .schema import (
    VitalsignCreate,
    VitalsignUpdate,
    ConsultationCreate,
    ConsultationUpdate,
    PrescriptionCreate,
    PrescriptionUpdate,
    LabtestCreate,
    LabtestUpdate,
)
from patient import repository as patient_repo
from user import repository as user_repo
from appointment import repository as appointment_repo
from utils.utils import utc_now, LabTestStatus


# ======================== Vitalsign Handlers ========================
def create_vitalsign(db: Session, data: VitalsignCreate):
    patient = patient_repo.get_by_id(db, data.patient_id)
    if not patient:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")
    user = user_repo.get_by_id(db, data.recorded_by_user_id)
    if not user:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Recording user not found")

    try:
        obj = repository.create(db, data.model_dump())
        db.commit()
        db.refresh(obj)
        return obj
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Resource already exists.")
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Referenced resource does not exist.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def list_vitalsigns(db: Session, limit: int, offset: int, **filters) -> dict:
    items = repository.list_all(db, limit=limit, offset=offset, **filters)
    total = repository.count_all(db, **filters)
    return {"items": items, "total": total, "limit": limit, "offset": offset}


def get_vitalsign_by_id(db: Session, entity_id: str):
    obj = repository.get_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Vital sign not found")
    return obj


def update_vitalsign(db: Session, entity_id: str, data: VitalsignUpdate):
    obj = repository.get_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Vital sign not found")

    update_data = data.model_dump(exclude_unset=True)
    if "patient_id" in update_data and update_data["patient_id"]:
        patient = patient_repo.get_by_id(db, update_data["patient_id"])
        if not patient:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")
    if "recorded_by_user_id" in update_data and update_data["recorded_by_user_id"]:
        user = user_repo.get_by_id(db, update_data["recorded_by_user_id"])
        if not user:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Recording user not found")

    try:
        updated = repository.update(db, entity_id, update_data)
        db.commit()
        db.refresh(updated)
        return updated
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Resource already exists.")
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Referenced resource does not exist.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def delete_vitalsign(db: Session, entity_id: str):
    obj = repository.get_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Vital sign not found")

    try:
        repository.delete(db, entity_id)
        db.commit()
        return {"detail": "Vital sign deleted successfully"}
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Cannot delete vital sign while it is referenced by other records.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


# ======================== Consultation Handlers ========================
def create_consultation(db: Session, data: ConsultationCreate):
    patient = patient_repo.get_by_id(db, data.patient_id)
    if not patient:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")
    doctor = user_repo.get_by_id(db, data.doctor_id)
    if not doctor:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Doctor not found")
    if data.appointment_id:
        appt = appointment_repo.get_by_id(db, data.appointment_id)
        if not appt:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Appointment not found")

    try:
        obj = repository.create_consultation(db, data.model_dump())
        db.commit()
        db.refresh(obj)
        return obj
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="A consultation already exists for this appointment.")
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Referenced resource does not exist.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def list_consultations(db: Session, limit: int, offset: int, **filters) -> dict:
    items = repository.list_consultations(db, limit=limit, offset=offset, **filters)
    total = repository.count_consultations(db, **filters)
    return {"items": items, "total": total, "limit": limit, "offset": offset}


def get_consultation_by_id(db: Session, entity_id: str):
    obj = repository.get_consultation_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Consultation not found")
    return obj


def get_consultation_details(db: Session, entity_id: str):
    obj = repository.get_consultation_with_details(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Consultation not found")
    return obj


def update_consultation(db: Session, entity_id: str, data: ConsultationUpdate):
    obj = repository.get_consultation_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Consultation not found")

    update_data = data.model_dump(exclude_unset=True)
    if "patient_id" in update_data and update_data["patient_id"]:
        patient = patient_repo.get_by_id(db, update_data["patient_id"])
        if not patient:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")
    if "doctor_id" in update_data and update_data["doctor_id"]:
        doctor = user_repo.get_by_id(db, update_data["doctor_id"])
        if not doctor:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Doctor not found")
    if "appointment_id" in update_data and update_data["appointment_id"]:
        appt = appointment_repo.get_by_id(db, update_data["appointment_id"])
        if not appt:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Appointment not found")

    try:
        updated = repository.update_consultation(db, entity_id, update_data)
        db.commit()
        db.refresh(updated)
        return updated
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="A consultation already exists for this appointment.")
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Referenced resource does not exist.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def delete_consultation(db: Session, entity_id: str):
    obj = repository.get_consultation_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Consultation not found")

    try:
        repository.delete_consultation(db, entity_id)
        db.commit()
        return {"detail": "Consultation deleted successfully"}
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Cannot delete consultation while it is referenced by other records.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def complete_consultation(db: Session, entity_id: str):
    obj = repository.get_consultation_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Consultation not found")

    if not obj.diagnosis:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Diagnosis is required to complete consultation")

    if obj.appointment_id:
        appt = appointment_repo.get_by_id(db, obj.appointment_id)
        if appt:
            try:
                appointment_repo.update(db, obj.appointment_id, {"status": "COMPLETED"})
            except Exception:
                pass

    try:
        db.commit()
        db.refresh(obj)
        return obj
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


# ======================== Prescription Handlers ========================
def create_prescription(db: Session, data: PrescriptionCreate):
    consultation = repository.get_consultation_by_id(db, data.consultation_id)
    if not consultation:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Consultation not found")
    patient = patient_repo.get_by_id(db, data.patient_id)
    if not patient:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")
    doctor = user_repo.get_by_id(db, data.doctor_id)
    if not doctor:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Doctor not found")

    try:
        obj = repository.create_prescription(db, data.model_dump())
        db.commit()
        db.refresh(obj)
        return obj
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Resource already exists.")
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Referenced resource does not exist.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def list_prescriptions(db: Session, limit: int, offset: int, **filters) -> dict:
    items = repository.list_prescriptions(db, limit=limit, offset=offset, **filters)
    total = repository.count_prescriptions(db, **filters)
    return {"items": items, "total": total, "limit": limit, "offset": offset}


def get_prescription_by_id(db: Session, entity_id: str):
    obj = repository.get_prescription_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Prescription not found")
    return obj


def update_prescription(db: Session, entity_id: str, data: PrescriptionUpdate):
    obj = repository.get_prescription_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Prescription not found")

    update_data = data.model_dump(exclude_unset=True)
    if "consultation_id" in update_data and update_data["consultation_id"]:
        consultation = repository.get_consultation_by_id(db, update_data["consultation_id"])
        if not consultation:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Consultation not found")
    if "patient_id" in update_data and update_data["patient_id"]:
        patient = patient_repo.get_by_id(db, update_data["patient_id"])
        if not patient:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")
    if "doctor_id" in update_data and update_data["doctor_id"]:
        doctor = user_repo.get_by_id(db, update_data["doctor_id"])
        if not doctor:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Doctor not found")

    try:
        updated = repository.update_prescription(db, entity_id, update_data)
        db.commit()
        db.refresh(updated)
        return updated
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Resource already exists.")
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Referenced resource does not exist.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def delete_prescription(db: Session, entity_id: str):
    obj = repository.get_prescription_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Prescription not found")

    try:
        repository.delete_prescription(db, entity_id)
        db.commit()
        return {"detail": "Prescription deleted successfully"}
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Cannot delete prescription while it is referenced by other records.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


# ======================== Labtest Handlers ========================
def create_labtest(db: Session, data: LabtestCreate):
    patient = patient_repo.get_by_id(db, data.patient_id)
    if not patient:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")
    doctor = user_repo.get_by_id(db, data.ordered_by_doctor_id)
    if not doctor:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Doctor not found")
    if data.consultation_id:
        consultation = repository.get_consultation_by_id(db, data.consultation_id)
        if not consultation:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Consultation not found")

    try:
        obj = repository.create_labtest(db, data.model_dump())
        db.commit()
        db.refresh(obj)
        return obj
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Resource already exists.")
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Referenced resource does not exist.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def list_labtests(db: Session, limit: int, offset: int, **filters) -> dict:
    items = repository.list_labtests(db, limit=limit, offset=offset, **filters)
    total = repository.count_labtests(db, **filters)
    return {"items": items, "total": total, "limit": limit, "offset": offset}


def get_labtest_by_id(db: Session, entity_id: str):
    obj = repository.get_labtest_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Lab test not found")
    return obj


def update_labtest(db: Session, entity_id: str, data: LabtestUpdate):
    obj = repository.get_labtest_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Lab test not found")

    update_data = data.model_dump(exclude_unset=True)
    if "patient_id" in update_data and update_data["patient_id"]:
        patient = patient_repo.get_by_id(db, update_data["patient_id"])
        if not patient:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")
    if "ordered_by_doctor_id" in update_data and update_data["ordered_by_doctor_id"]:
        doctor = user_repo.get_by_id(db, update_data["ordered_by_doctor_id"])
        if not doctor:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Doctor not found")
    if "consultation_id" in update_data and update_data["consultation_id"]:
        consultation = repository.get_consultation_by_id(db, update_data["consultation_id"])
        if not consultation:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Consultation not found")

    try:
        updated = repository.update_labtest(db, entity_id, update_data)
        db.commit()
        db.refresh(updated)
        return updated
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Resource already exists.")
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Referenced resource does not exist.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def delete_labtest(db: Session, entity_id: str):
    obj = repository.get_labtest_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Lab test not found")

    try:
        repository.delete_labtest(db, entity_id)
        db.commit()
        return {"detail": "Lab test deleted successfully"}
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "foreign key" in msg:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Cannot delete lab test while it is referenced by other records.")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def collect_sample(db: Session, entity_id: str):
    obj = repository.get_labtest_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Lab test not found")

    if obj.status != LabTestStatus.ORDERED:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Can only collect sample for tests in 'ordered' status")

    try:
        update_data = {
            "status": LabTestStatus.SAMPLE_COLLECTED,
            "sample_collected_date": utc_now(),
        }
        updated = repository.update_labtest(db, entity_id, update_data)
        db.commit()
        db.refresh(updated)
        return updated
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def enter_results(db: Session, entity_id: str, result_value: str, result_unit: str = None, reference_range: str = None):
    obj = repository.get_labtest_by_id(db, entity_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Lab test not found")

    if obj.status == LabTestStatus.ORDERED:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Sample must be collected before entering results")
    if obj.status == LabTestStatus.CANCELLED:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot enter results for cancelled test")

    try:
        update_data = {
            "status": LabTestStatus.COMPLETED,
            "result_value": result_value,
            "result_date": utc_now(),
        }
        if result_unit:
            update_data["result_unit"] = result_unit
        if reference_range:
            update_data["reference_range"] = reference_range
        updated = repository.update_labtest(db, entity_id, update_data)
        db.commit()
        db.refresh(updated)
        return updated
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise