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.patients_repository import PatientsRepository
from app.application.scheduled_appointments_repository import ScheduledAppointmentsRepository
from app.application.users_repository import UsersRepository
from app.domain.entities.scheduled_appointment import ScheduledAppointmentEntity
from app.domain.models import ScheduledAppointment


class ScheduledAppointmentsService:
    def __init__(self, db: AsyncSession):
        self.scheduled_appointment_repository = ScheduledAppointmentsRepository(db)
        self.appointment_types_repository = AppointmentTypesRepository(db)
        self.users_repository = UsersRepository(db)
        self.patients_repository = PatientsRepository(db)

    async def get_all_scheduled_appointments(self) -> list[ScheduledAppointmentEntity]:
        scheduled_appointments = await self.scheduled_appointment_repository.get_all()

        return [
            self.model_to_entity(scheduled_appointment)
            for scheduled_appointment in scheduled_appointments
        ]

    async def get_scheduled_appointments_by_doctor_id(self, doctor_id: int) -> Optional[
        list[ScheduledAppointmentEntity]
    ]:
        doctor = 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',
            )

        scheduled_appointments = await self.scheduled_appointment_repository.get_by_doctor_id(doctor_id)

        return [
            self.model_to_entity(scheduled_appointment)
            for scheduled_appointment in scheduled_appointments
        ]

    async def get_scheduled_appointments_by_patient_id(self, patient_id: int) -> Optional[
        list[ScheduledAppointmentEntity]
    ]:
        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',
            )

        scheduled_appointments = await self.scheduled_appointment_repository.get_by_patient_id(patient_id)

        return [
            self.model_to_entity(scheduled_appointment)
            for scheduled_appointment in scheduled_appointments
        ]

    async def create_scheduled_appointment(self, scheduled_appointment: ScheduledAppointmentEntity, doctor_id: int) -> \
            Optional[
                ScheduledAppointmentEntity
            ]:
        patient = await self.patients_repository.get_by_id(scheduled_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',
            )

        scheduled_appointment.doctor_id = doctor_id

        appointment_type = await self.appointment_types_repository.get_by_id(scheduled_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',
            )

        scheduled_appointment_model = self.entity_to_model(scheduled_appointment)

        await self.scheduled_appointment_repository.create(scheduled_appointment_model)

        return self.model_to_entity(scheduled_appointment_model)

    async def cancel_scheduled_appointment(self, scheduled_appointment_id: int, doctor_id):
        scheduled_appointment_model = await self.scheduled_appointment_repository.get_by_id(scheduled_appointment_id)

        if not scheduled_appointment_model:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Scheduled appointment not found")

        if scheduled_appointment_model.is_canceled:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
                                detail="Scheduled appointment already cancelled")

        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',
            )

        if scheduled_appointment_model.doctor_id != doctor_id and doctor.role.title != 'Администратор':
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail='Permission denied',
            )

        scheduled_appointment_model.is_canceled = True

        await self.scheduled_appointment_repository.update(scheduled_appointment_model)

        return self.model_to_entity(scheduled_appointment_model)

    async def update_scheduled_appointment(
            self,
            scheduled_appointment_id: int,
            scheduled_appointment: ScheduledAppointmentEntity
    ) -> Optional[ScheduledAppointmentEntity]:
        scheduled_appointment_model = await self.scheduled_appointment_repository.get_by_id(scheduled_appointment_id)

        if not scheduled_appointment_model:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Scheduled appointment not found")

        patient = await self.patients_repository.get_by_id(scheduled_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(scheduled_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(scheduled_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',
            )

        scheduled_appointment_model.scheduled_datetime = scheduled_appointment.scheduled_datetime
        scheduled_appointment_model.patient_id = scheduled_appointment.patient_id
        scheduled_appointment_model.doctor_id = scheduled_appointment.doctor_id
        scheduled_appointment_model.type_id = scheduled_appointment.type_id
        scheduled_appointment_model.is_canceled = scheduled_appointment.is_canceled

        await self.scheduled_appointment_repository.update(scheduled_appointment_model)

        return self.model_to_entity(scheduled_appointment_model)

    @staticmethod
    def entity_to_model(scheduled_appointment: ScheduledAppointmentEntity) -> ScheduledAppointment:
        scheduled_appointment_model = ScheduledAppointment(
            scheduled_datetime=scheduled_appointment.scheduled_datetime,
            patient_id=scheduled_appointment.patient_id,
            doctor_id=scheduled_appointment.doctor_id,
            type_id=scheduled_appointment.type_id,
            is_canceled=scheduled_appointment.is_canceled,
        )

        if scheduled_appointment.id:
            scheduled_appointment_model.id = scheduled_appointment.id

        return scheduled_appointment_model

    @staticmethod
    def model_to_entity(scheduled_appointment: ScheduledAppointment) -> ScheduledAppointmentEntity:
        scheduled_appointment_entity = ScheduledAppointmentEntity(
            id=scheduled_appointment.id,
            scheduled_datetime=scheduled_appointment.scheduled_datetime,
            patient_id=scheduled_appointment.patient_id,
            doctor_id=scheduled_appointment.doctor_id,
            type_id=scheduled_appointment.type_id,
            is_canceled=scheduled_appointment.is_canceled,
        )

        if scheduled_appointment.patient is not None:
            scheduled_appointment_entity.patient = scheduled_appointment.patient

        if scheduled_appointment.doctor is not None:
            scheduled_appointment_entity.doctor = scheduled_appointment.doctor

        if scheduled_appointment.type is not None:
            scheduled_appointment_entity.type = scheduled_appointment.type

        return scheduled_appointment_entity
