from datetime import datetime, timedelta
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, func, select
from sqlalchemy.orm import relationship
from db import Base, CRUD
from utils import logger, answer, toDict


class Signing(Base):
    __tablename__ = "signings"

    id = Column(Integer, primary_key=True, index=True)
    userIdLpu = Column(
        String, ForeignKey("practitioners.userIdLpu", ondelete="CASCADE")
    )
    idPatientMis = Column(
        String, ForeignKey("patients.idPatientMis", ondelete="CASCADE")
    )
    storagePath = Column(String, nullable=True)
    trackingId = Column(String, nullable=True)
    documents = relationship(
        "Document",
        cascade="all, delete-orphan",
        lazy="joined",
        uselist=True,
        single_parent=True,
    )
    statuses = relationship(
        "Statuses",
        cascade="all, delete-orphan",
        lazy="joined",
        uselist=True,
        single_parent=True,
    )
    created_at = Column(DateTime, default=datetime.now)
    updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

    def toDict(self):
        return toDict(self)

    async def save(self) -> "Signing":
        return await CRUD.create(self, refresh=True)

    async def edit(self, **kwargs) -> "Signing":
        return await CRUD.update(Signing, self.id, **kwargs)

    async def delete(self) -> bool:
        return await CRUD.delete(self)

    @staticmethod
    async def addSigning(userIdLpu: str, idPatientMis: str):
        from utils.background import enable_job

        if type(userIdLpu) != str:
            userIdLpu = str(userIdLpu)
        if type(idPatientMis) != str:
            idPatientMis = str(idPatientMis)

        enable_job()

        return await Signing(userIdLpu=userIdLpu, idPatientMis=idPatientMis).save()

    @staticmethod
    async def getSigningById(id: int, toDict: bool = True):
        singing = await CRUD.read(select(Signing).where(Signing.id == id))
        if singing:
            data = singing.toDict() if toDict else singing
            return answer(data=data)
        logger.error(f"Подписание не найдено, id: {id}")
        return answer(success=False, message="Подписание не найдено")

    @staticmethod
    async def getSigningsByIdPatientMis(idPatientMis: str, toDict: bool = True):
        if type(idPatientMis) != str:
            idPatientMis = str(idPatientMis)
        signings = await CRUD.read(
            select(Signing).where(Signing.idPatientMis == idPatientMis), True
        )
        if signings:
            data = [signing.toDict() for signing in signings] if toDict else signings
            return answer(data=data)
        logger.error(f"Подписания не найдены, idPatientMis: {idPatientMis}")
        return answer(success=False, message="Подписания не найдены")

    @staticmethod
    async def getSigningsByUserIdLpu(userIdLpu: str, toDict: bool = True):
        signings = await CRUD.read(
            select(Signing).where(Signing.userIdLpu == userIdLpu), True
        )
        if signings:
            data = [signing.toDict() for signing in signings] if toDict else signings
            return answer(data=data)
        logger.error(f"Подписания не найдены, userIdLpu: {userIdLpu}")
        return answer(success=False, message="Подписания не найдены")

    @staticmethod
    async def getSigningsByUserIdLpuAndIdPatientMis(
        userIdLpu: str, idPatientMis: str, toDict: bool = True
    ):
        signings = await CRUD.read(
            select(Signing)
            .where(Signing.userIdLpu == userIdLpu)
            .where(Signing.idPatientMis == idPatientMis),
            True,
        )
        if signings:
            data = [signing.toDict() for signing in signings] if toDict else signings
            return answer(data=data)
        logger.error(
            f"Подписания не найдены, userIdLpu: {userIdLpu}, idPatientMis: {idPatientMis}"
        )
        return answer(success=False, message="Подписания не найдены")

    @staticmethod
    async def updateStatuses():
        checkList = []

        singingsInProgress = await CRUD.read(
            select(Signing).where(Signing.storagePath == None), True
        )

        if singingsInProgress and len(singingsInProgress) > 0:
            for signing in singingsInProgress:
                if len(signing.statuses) > 0:
                    maxCode = max([status.status for status in signing.statuses])
                    if maxCode >= 204:
                        continue
                checkList.append(signing.id)

            logger.info(f"Проверка статусов для {len(checkList)} подписаний")

        if len(checkList) > 0:
            from db.schemas.statuses import Statuses

            for id in checkList:
                await Statuses.updateStatus(id)
        else:
            from utils.background import disable_job

            logger.info("Нет подписаний для проверки")
            disable_job()

    @staticmethod
    async def getFilteredSingings(userIdLpu: str, filters: dict):
        def checkResult(result):
            if not result or len(result) == 0:
                return False
            else:
                return True

        userFilter = None

        match filters.get("sender"):
            case "my":
                userFilter = True
            case "not_my":
                userFilter = False
            case _:
                pass

        periodFilterStart = None
        periodFilterStop = None

        match filters.get("period"):
            case "today":
                periodFilterStart = datetime.now().replace(
                    hour=0, minute=0, second=0, microsecond=0
                )
            case "3days":
                periodFilterStart = (datetime.now() - timedelta(days=2)).replace(
                    hour=0, minute=0, second=0, microsecond=0
                )
            case "7days":
                periodFilterStart = (datetime.now() - timedelta(days=6)).replace(
                    hour=0, minute=0, second=0, microsecond=0
                )
            case "30days":
                periodFilterStart = (datetime.now() - timedelta(days=30)).replace(
                    hour=0, minute=0, second=0, microsecond=0
                )
            case "90days":
                periodFilterStart = (datetime.now() - timedelta(days=90)).replace(
                    hour=0, minute=0, second=0, microsecond=0
                )
            case "custom":
                periodFilterStart = datetime.strptime(
                    filters.get("dateFrom"), "%Y-%m-%d"
                )
                periodFilterStop = datetime.strptime(filters.get("dateTo"), "%Y-%m-%d")
                if periodFilterStop < periodFilterStart:
                    periodFilterStop, periodFilterStart = (
                        periodFilterStart,
                        periodFilterStop,
                    )
            case _:
                pass

        query = select(Signing)

        if userFilter is not None:
            if userFilter:  # True - только свои
                query = query.where(Signing.userIdLpu == userIdLpu)
            else:  # False - все кроме своих
                query = query.where(Signing.userIdLpu != userIdLpu)

        if periodFilterStart is not None:
            query = query.where(Signing.created_at >= periodFilterStart)
        if periodFilterStop is not None:
            query = query.where(Signing.created_at <= periodFilterStop)

        query = query.order_by(Signing.created_at.desc())

        result = await CRUD.read(query, True)

        if not checkResult(result):
            return answer(data=[])

        match filters.get("status"):
            case "processing":
                result = [
                    signing
                    for signing in result
                    if (signing.storagePath is None and signing.trackingId is not None)
                ]
            case "completed":
                result = [
                    signing for signing in result if signing.storagePath is not None
                ]
            case "error":
                result = [
                    signing
                    for signing in result
                    if (signing.trackingId is None and signing.storagePath is None)
                ]
            case _:
                pass

        if not checkResult(result):
            return answer(data=[])

        esiaFilter = None

        match filters.get("esia", filters.get("signatureType")):
            case "esia":
                esiaFilter = True
            case "not_esia":
                esiaFilter = False
            case _:
                pass

        from db.schemas.practitioners import Practitioner

        userIdLpus = set([signing.userIdLpu for signing in result])

        query = select(Practitioner).where(Practitioner.userIdLpu.in_(userIdLpus))

        if filters.get("senderName") is not None and filters.get("senderName") != "":
            query = query.where(
                func.lower(Practitioner.familyName).like(
                    "%" + filters.get("senderName").lower() + "%"
                )
            )

        practitioners = await CRUD.read(query, True)

        if not practitioners or len(practitioners) == 0:
            result = []
        else:
            practitionersData = {
                practitioner.userIdLpu: {
                    "userName": practitioner.name,
                    "esiaAuth": practitioner.esiaAuth,
                }
                for practitioner in practitioners
            }

            result = [signing.toDict() for signing in result]

            match esiaFilter:
                case True:
                    result = [
                        {
                            **signing,
                            "userName": practitionersData[signing.get("userIdLpu")][
                                "userName"
                            ],
                            "esiaAuth": practitionersData[signing.get("userIdLpu")][
                                "esiaAuth"
                            ],
                        }
                        for signing in result
                        if practitionersData[signing.get("userIdLpu")]["esiaAuth"]
                    ]
                case False:
                    result = [
                        {
                            **signing,
                            "userName": practitionersData[signing.get("userIdLpu")][
                                "userName"
                            ],
                            "esiaAuth": practitionersData[signing.get("userIdLpu")][
                                "esiaAuth"
                            ],
                        }
                        for signing in result
                        if not practitionersData[signing.get("userIdLpu")]["esiaAuth"]
                    ]
                case _:
                    result = [
                        {
                            **signing,
                            "userName": practitionersData[signing.get("userIdLpu")][
                                "userName"
                            ],
                            "esiaAuth": practitionersData[signing.get("userIdLpu")][
                                "esiaAuth"
                            ],
                        }
                        for signing in result
                    ]

        if esiaFilter is not None and not checkResult(result):
            return answer(data=[])

        from db.schemas.patients import Patient

        idPatientMises = set([signing.get("idPatientMis") for signing in result])

        query = select(Patient).where(Patient.idPatientMis.in_(idPatientMises))

        if filters.get("patientName") is not None and filters.get("patientName") != "":
            query = query.where(
                func.lower(Patient.name).like(
                    "%" + filters.get("patientName").lower() + "%"
                )
            )

        patients = await CRUD.read(query, True)

        if not patients or len(patients) == 0:
            result = []
        else:
            patientsData = {patient.idPatientMis: patient.name for patient in patients}

            result = [
                {
                    **signing,
                    "patientName": patientsData[signing.get("idPatientMis")],
                }
                for signing in result
            ]

        if (
            filters.get("documentNumber") is not None
            and filters.get("documentNumber") != ""
        ):
            try:
                search_number = int(filters.get("documentNumber"))
                result = [
                    signing
                    for signing in result
                    if any(
                        doc.get("number") == search_number
                        for doc in signing.get("documents", [])
                    )
                ]
            except ValueError:
                # Если не удалось преобразовать в число, пропускаем фильтр
                pass

        return answer(data=result)
