from typing import Optional

from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from starlette import status

from app.application.appointment_types_repository import AppointmentTypesRepository
from app.application.appointments_repository import AppointmentsRepository
from app.application.patients_repository import PatientsRepository
from app.application.users_repository import UsersRepository
from app.domain.entities.appointment import AppointmentEntity
from app.domain.models import Appointment
from app.infrastructure.appointment_types_service import AppointmentTypesService
from app.infrastructure.patients_service import PatientsService
from app.infrastructure.users_service import UsersService


class AppointmentsService:
    def __init__(self, db: AsyncSession):
        self.appointments_repository = AppointmentsRepository(db)
        self.appointment_types_repository = AppointmentTypesRepository(db)
        self.users_repository = UsersRepository(db)
        self.patients_repository = PatientsRepository(db)

    async def get_all_appointments(self) -> list[AppointmentEntity]:
        appointments = await self.appointments_repository.get_all()

        return [
            self.model_to_entity(appointment)
            for appointment in appointments
        ]

    async def get_appointments_by_doctor_id(self, doctor_id: int) -> Optional[list[AppointmentEntity]]:
        doctor = await self.users_repository.get_by_id(doctor_id)

        if not doctor:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='The doctor with this ID was not found',
            )

        appointments = await self.appointments_repository.get_by_doctor_id(doctor_id)

        return [
            self.model_to_entity(appointment)
            for appointment in appointments
        ]

    async def get_appointments_by_patient_id(self, patient_id: int) -> Optional[list[AppointmentEntity]]:
        patient = await self.patients_repository.get_by_id(patient_id)

        if not patient:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='The patient with this ID was not found',
            )

        appointments = await self.appointments_repository.get_by_patient_id(patient_id)

        return [
            self.model_to_entity(appointment)
            for appointment in appointments
        ]

    async def create_appointment(self, appointment: AppointmentEntity, doctor_id: int) -> Optional[AppointmentEntity]:
        patient = await self.patients_repository.get_by_id(appointment.patient_id)

        if not patient:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='The patient with this ID was not found',
            )

        doctor = await self.users_repository.get_by_id(doctor_id)

        if not doctor:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='The doctor/user with this ID was not found',
            )

        appointment.doctor_id = doctor_id

        appointment_type = await self.appointment_types_repository.get_by_id(appointment.type_id)

        if not appointment_type:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='The appointment type with this ID was not found',
            )

        appointment_model = self.entity_to_model(appointment)

        await self.appointments_repository.create(appointment_model)

        return self.model_to_entity(appointment_model)

    async def update_appointment(self, appointment_id: int, appointment: AppointmentEntity) -> Optional[
        AppointmentEntity
    ]:
        appointment_model = await self.appointments_repository.get_by_id(appointment_id)

        if not appointment_model:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Appointment not found")

        patient = await self.patients_repository.get_by_id(appointment.patient_id)

        if not patient:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='The patient with this ID was not found',
            )

        doctor = await self.users_repository.get_by_id(appointment.doctor_id)

        if not doctor:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='The doctor/user with this ID was not found',
            )

        appointment_type = await self.appointment_types_repository.get_by_id(appointment.type_id)

        if not appointment_type:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='The appointment type with this ID was not found',
            )

        appointment_model.results = appointment.results
        appointment_model.days_until_the_next_appointment = appointment.days_until_the_next_appointment
        appointment_model.appointment_datetime = appointment.appointment_datetime
        appointment_model.patient_id = appointment.patient_id
        appointment_model.doctor_id = appointment.doctor_id
        appointment_model.type_id = appointment.type_id

        await self.appointments_repository.update(appointment_model)

        return self.model_to_entity(appointment_model)

    @staticmethod
    def entity_to_model(appointment: AppointmentEntity) -> Appointment:
        appointment_model = Appointment(
            results=appointment.results,
            days_until_the_next_appointment=appointment.days_until_the_next_appointment,
            appointment_datetime=appointment.appointment_datetime,
            patient_id=appointment.patient_id,
            doctor_id=appointment.doctor_id,
            type_id=appointment.type_id,
        )

        if appointment.id is not None:
            appointment_model.id = appointment.id

        return appointment_model

    @staticmethod
    def model_to_entity(appointment: Appointment) -> AppointmentEntity:
        appointment_entity = AppointmentEntity(
            id=appointment.id,
            results=appointment.results,
            days_until_the_next_appointment=appointment.days_until_the_next_appointment,
            appointment_datetime=appointment.appointment_datetime,
            patient_id=appointment.patient_id,
            doctor_id=appointment.doctor_id,
            type_id=appointment.type_id,
        )

        if appointment.patient is not None:
            appointment_entity.patient = PatientsService.model_to_entity(appointment.patient)

        if appointment.doctor is not None:
            appointment_entity.doctor = UsersService.model_to_entity(appointment.doctor)

        if appointment.type is not None:
            appointment_entity.type = AppointmentTypesService.model_to_entity(appointment.type)

        return appointment_entity
