from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from fastapi import HTTPException, status
from typing import Optional
from . import repository
from .schema import AppointmentCreate, AppointmentUpdate
from patient import repository as patient_repo
from user import repository as user_repo
from department import repository as department_repo
from utils.utils import AppointmentStatus


def create_appointment(db: Session, data: AppointmentCreate):
    if not patient_repo.get_by_id(db, data.patient_id):
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")
    if not user_repo.get_by_id(db, data.doctor_id):
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Doctor not found")
    if data.department_id and not department_repo.get_by_id(db, data.department_id):
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Department 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="Appointment 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_appointments(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_appointment(db: Session, appointment_id: str):
    obj = repository.get_by_id(db, appointment_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Appointment not found")
    return obj


def update_appointment(db: Session, appointment_id: str, data: AppointmentUpdate):
    obj = repository.get_by_id(db, appointment_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Appointment not found")

    update_dict = data.model_dump(exclude_unset=True)
    if "patient_id" in update_dict and not patient_repo.get_by_id(db, update_dict["patient_id"]):
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")
    if "doctor_id" in update_dict and not user_repo.get_by_id(db, update_dict["doctor_id"]):
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Doctor not found")
    if "department_id" in update_dict and update_dict["department_id"] is not None:
        if not department_repo.get_by_id(db, update_dict["department_id"]):
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Department not found")

    try:
        obj = repository.update(db, appointment_id, update_dict)
        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="Duplicate constraint violation.")
        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_appointment(db: Session, appointment_id: str):
    obj = repository.get_by_id(db, appointment_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Appointment not found")

    try:
        repository.delete(db, appointment_id)
        db.commit()
        return {"detail": "Appointment 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 appointment while 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 get_appointment_details(db: Session, appointment_id: str):
    obj = repository.get_with_details(db, appointment_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Appointment not found")
    return obj


def check_in_appointment(db: Session, appointment_id: str):
    obj = repository.get_by_id(db, appointment_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Appointment not found")
    if obj.status not in [AppointmentStatus.SCHEDULED, AppointmentStatus.CONFIRMED]:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Only scheduled or confirmed appointments can be checked in.",
        )

    try:
        obj = repository.update(db, appointment_id, {"status": AppointmentStatus.CHECKED_IN})
        db.commit()
        db.refresh(obj)
        return obj
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def cancel_appointment(db: Session, appointment_id: str):
    obj = repository.get_by_id(db, appointment_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Appointment not found")
    if obj.status in [AppointmentStatus.COMPLETED, AppointmentStatus.CANCELLED, AppointmentStatus.NO_SHOW]:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot cancel an appointment that is already completed, cancelled, or marked no show.",
        )

    try:
        obj = repository.update(db, appointment_id, {"status": AppointmentStatus.CANCELLED})
        db.commit()
        db.refresh(obj)
        return obj
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def complete_appointment(db: Session, appointment_id: str):
    obj = repository.get_by_id(db, appointment_id)
    if not obj:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Appointment not found")
    if obj.status not in [AppointmentStatus.CHECKED_IN, AppointmentStatus.IN_PROGRESS]:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Only checked-in or in-progress appointments can be completed.",
        )

    try:
        obj = repository.update(db, appointment_id, {"status": AppointmentStatus.COMPLETED})
        db.commit()
        db.refresh(obj)
        return obj
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise