import asyncio
from datetime import datetime, timedelta
from functools import wraps

from db.schemas import Settings
from utils import (
    logger,
    requestGET,
    requestPOSTMultipart,
    answer,
    detect_and_save_attachment,
    requestPOST,
)
from pathlib import Path


def getVerifySSL(url: str):
    return "demo" not in url.lower()


def rate_limit(max_calls: int, period: int):
    """
    Декоратор для ограничения количества вызовов функции
    :param max_calls: максимальное количество вызовов
    :param period: период в секундах
    """

    def decorator(func):
        calls = []

        @wraps(func)
        async def wrapper(*args, **kwargs):
            now = datetime.now()
            # Удаляем старые вызовы
            calls[:] = [
                call_time
                for call_time in calls
                if now - call_time < timedelta(seconds=period)
            ]

            if len(calls) >= max_calls:
                oldest_call = calls[0]
                wait_time = (
                    oldest_call + timedelta(seconds=period) - now
                ).total_seconds()
                if wait_time > 0:
                    logger.warning(
                        f"Превышен лимит запросов. Ожидание {wait_time:.2f} секунд"
                    )
                    await asyncio.sleep(wait_time)
                    # После ожидания рекурсивно вызываем функцию
                    return await wrapper(*args, **kwargs)

            calls.append(now)
            return await func(*args, **kwargs)

        return wrapper

    return decorator


@rate_limit(max_calls=50, period=900)
async def sendForSigning(inData: dict):
    settings = await Settings.getSettings(False)
    if not settings.success:
        logger.error("Настройки не инициализированы")
        return answer(success=False, message="Настройки не инициализированы")
    settings = settings.data
    meta = {
        "organization": {
            "idLpu": settings.n3healthXIdLpu,
        },
        "practitioner": inData.get("practitioner", {}),
        "patients": [inData.get("patient", {}), *inData.get("recipients", [])],
        "medDocument": inData.get("medDocument", {}),
        "delivery": [inData.get("deliveryType", "sms")],
    }

    headers = {
        "Authorization": settings.n3healthAutorization,
        "X-Id-Lpu": settings.n3healthXIdLpu,
    }

    response = await requestPOSTMultipart(
        url=f"{settings.n3healthHost}/signing",
        headers=headers,
        meta=meta,
        files=inData.get("files", []),
        verify_ssl=getVerifySSL(settings.n3healthHost),
    )
    return response


async def resend(trackingID: str, IdsPatientMis: list[str], deliveryType: str = "sms"):
    settings = await Settings.getSettings(False)
    if not settings.success:
        logger.error("Настройки не инициализированы")
        return answer(success=False, message="Настройки не инициализированы")
    settings = settings.data
    headers = {
        "Authorization": settings.n3healthAutorization,
        "X-Id-Lpu": settings.n3healthXIdLpu,
    }
    data = {
        "trackingId": trackingID,
        "IdPatientMis": [str(IdPatientMis) for IdPatientMis in IdsPatientMis],
        "delivery": [deliveryType],
    }
    response = await requestPOST(
        f"{settings.n3healthHost}/resend",
        headers=headers,
        json=data,
        verify_ssl=getVerifySSL(settings.n3healthHost),
    )

    logger.warning(response)

    return response


async def revoke(trackingID: str):
    settings = await Settings.getSettings(False)
    if not settings.success:
        logger.error("Настройки не инициализированы")
        return answer(success=False, message="Настройки не инициализированы")
    settings = settings.data
    headers = {
        "Authorization": settings.n3healthAutorization,
        "X-Id-Lpu": settings.n3healthXIdLpu,
    }
    response = await requestPOST(
        f"{settings.n3healthHost}/signing/{trackingID}/cancel",
        headers=headers,
        verify_ssl=getVerifySSL(settings.n3healthHost),
    )
    return response


async def checkConnection(host: str, authorization: str, xIdLpu: str):
    headers = {"Authorization": authorization, "X-Id-Lpu": xIdLpu}
    params = {
        "limit": 1,
        "offset": 0,
    }
    return await requestGET(
        f"{host}/signing",
        headers=headers,
        params=params,
        verify_ssl=getVerifySSL(host),
    )


async def getTrackingIDSigning(trackingID: str, singingId: int):
    settings = await Settings.getSettings(False)
    if not settings.success:
        logger.error("Настройки не инициализированы")
        return answer(success=False, message="Настройки не инициализированы")
    apiUrl = settings.data.n3healthHost
    path = f"/signing/{trackingID}"
    headers = {
        "Authorization": settings.data.n3healthAutorization,
        "X-Id-Lpu": settings.data.n3healthXIdLpu,
    }
    response = await requestGET(
        apiUrl + path, headers=headers, verify_ssl=getVerifySSL(apiUrl)
    )

    if not response.success:
        logger.error(response.message)
        return response

    patients = response.data.get("data", {}).get("patients", [])

    statuses = {"global": int(response.data.get("data", {}).get("status", 0))}

    for patient in patients:
        idPatientMis = patient.get("idPatientMis")
        if idPatientMis not in statuses:
            statuses[idPatientMis] = 0
        if patient.get("status", {}).get("code", 0) > statuses[idPatientMis]:
            statuses[idPatientMis] = patient.get("status", {}).get("code", 0)

    messages = []

    if statuses["global"] in [210, 211]:

        attachments = (
            response.data.get("data", {}).get("medDocument", {}).get("attachments", [])
        )

        if len(attachments) > 0:
            statuses["storagePath"] = []

            for idx, attachment in enumerate(attachments, start=1):
                content = attachment.get("content")
                if not content:
                    continue

                ATTACHMENTS_DIR = Path("src/attachments/import")
                result = detect_and_save_attachment(
                    b64_content=content,
                    output_dir=ATTACHMENTS_DIR / str(singingId),
                    filename_prefix=f"document_{idx}",
                    save_attachments=True,
                )

                if not result:
                    message = f"Файл 'document_{idx}' не сохранены"
                    logger.error(message)
                    messages.append(message)
                else:
                    statuses["storagePath"].append(str(result["path"]))

    return answer(data=statuses, message=", ".join(messages))
