import asyncio
import logging
from dataclasses import dataclass
from io import BufferedReader

import pycurl

from gateway_core.application.ports.DownloadPort import DownloadPort
from gateway_core.application.ports.UploadPort import UploadPort
from gateway_core.domain.FileSystemData import TransferStatus

logger = logging.getLogger(__name__)


@dataclass
class TransferStatusDto:
    progress: int = 0
    state: TransferStatus = TransferStatus.IDLE


class FileTransferClient(UploadPort, DownloadPort):
    def __init__(self):
        self._uploadStatus: TransferStatusDto = TransferStatusDto()
        self._downloadStatus: TransferStatusDto = TransferStatusDto()

    def getCurrentUploadStatus(self) -> TransferStatusDto:
        """returns the current upload status"""
        return self._uploadStatus

    def getCurrentDownloadStatus(self) -> TransferStatusDto:
        """returns the current upload status"""
        return self._downloadStatus

    def resetUploadStatus(self) -> None:
        self._uploadStatus.state = TransferStatus.IDLE
        self._uploadStatus.progress = 0

    def resetDownloadStatus(self) -> None:
        self._downloadStatus.state = TransferStatus.IDLE
        self._downloadStatus.progress = 0

    async def uploadFile(self, file: BufferedReader, fileSize: int, url: str) -> bool:
        """
        Uploads a file to given url.

        :param file: file to upload
        :param fileSize: file size in bytes
        :param url: url to upload
        :return: bool success of the upload
        """
        logger.info("Uploading file...")

        loop = asyncio.get_event_loop()
        isSuccess = await loop.run_in_executor(None, self._transferFile, url, file, fileSize, False)

        if isSuccess:
            self._uploadStatus.state = TransferStatus.COMPLETED
        else:
            self._uploadStatus.state = TransferStatus.FAILED

        return isSuccess

    async def downloadFile(self, file: BufferedReader, url: str) -> bool:
        """
        Downloads a file from a given url.

        :param file: file, for downloaded file
        :param url: url to download
        :return: bool success of the download
        """
        logger.info("Downloading file...")

        loop = asyncio.get_event_loop()
        isSuccess = await loop.run_in_executor(None, self._transferFile, url, file, None, True)

        if isSuccess:
            self._downloadStatus.state = TransferStatus.COMPLETED
        else:
            self._downloadStatus.state = TransferStatus.FAILED

        return isSuccess

    def _transferFile(self, remoteUrl, file, fileSize, isDownload, certificatePath=None):
        """
        Performs the actual file transfer with curl.
        """
        handle = pycurl.Curl()
        try:
            handle.setopt(pycurl.URL, remoteUrl)
            handle.setopt(pycurl.CONNECTTIMEOUT, 20)

            if isDownload:
                handle.setopt(pycurl.WRITEDATA, file)
            else:
                handle.setopt(pycurl.INFILE, file)
                handle.setopt(pycurl.INFILESIZE, fileSize)
                handle.setopt(pycurl.UPLOAD, True)

            # Remove specific certificate since pycurl's default path points to non-existing file
            handle.setopt(pycurl.CAINFO, None)
            # Set specified certificate directory
            # handle.setopt(pycurl.CAPATH, str(certificatePath))

            handle.setopt(pycurl.NOPROGRESS, False)
            handle.setopt(pycurl.XFERINFOFUNCTION, self.callbackProgress)

            # Perform the actual file transfer
            handle.perform()

            code = handle.getinfo(pycurl.RESPONSE_CODE)
            if code in [200, 226, 0]:
                return True
            return False
        except (pycurl.error, OSError) as e:
            logger.error(f"Transfer Error: {e}")
            return False
        finally:
            handle.close()

    def callbackProgress(self, downloadTotal, downloaded, uploadTotal, uploaded):
        """
        Updates the upload progress.
        """
        logger.debug(
            f'upd: {uploaded} - upt: {uploadTotal} \n- \r{((uploaded / uploadTotal) * 100 if uploadTotal else 0):.2f}%',
            end='')
        if uploadTotal:
            self._uploadStatus.state = TransferStatus.TRANSFERRING
            self._uploadStatus.progress = (uploaded / uploadTotal) * 100

        if downloadTotal:
            self._downloadStatus.state = TransferStatus.TRANSFERRING
            self._downloadStatus.progress = (downloaded / downloadTotal) * 100
