import os
import re
import sys
import tarfile
import zipfile

from webdriver_manager.core.archive import Archive, LinuxZipFileWithPermissions
from webdriver_manager.core.os_manager import OperationSystemManager


class File(object):
    def __init__(self, stream, file_name):
        self.content = stream.content
        self.__stream = stream
        self.file_name = file_name
        self.__temp_name = "driver"
        self.__regex_filename = r"""filename.+"(.+)"|filename.+''(.+)|filename=([\w.-]+)"""

    @property
    def filename(self) -> str:
        if self.file_name:
            return self.file_name
        try:
            content = self.__stream.headers["content-disposition"]

            content_disposition_list = re.split(";", content)
            filenames = [re.findall(self.__regex_filename, element) for element in content_disposition_list]
            filename = next(filter(None, next(filter(None, next(filter(None, filenames))))))
        except KeyError:
            filename = f"{self.__temp_name}.zip"
        except (IndexError, StopIteration):
            filename = f"{self.__temp_name}.exe"

        if '"' in filename:
            filename = filename.replace('"', "")

        return filename


class FileManager(object):

    def __init__(self, os_system_manager: OperationSystemManager):
        self._os_system_manager = os_system_manager

    def save_archive_file(self, file: File, directory: str):
        os.makedirs(directory, exist_ok=True)

        archive_path = f"{directory}{os.sep}{file.filename}"
        with open(archive_path, "wb") as code:
            code.write(file.content)
        if not os.path.exists(archive_path):
            raise FileExistsError(f"No file has been saved on such path {archive_path}")
        return Archive(archive_path)

    def unpack_archive(self, archive_file: Archive, target_dir):
        file_path = archive_file.file_path
        if file_path.endswith(".zip"):
            return self.__extract_zip(archive_file, target_dir)
        elif file_path.endswith(".tar.gz"):
            return self.__extract_tar_file(archive_file, target_dir)

    def __extract_zip(self, archive_file, to_directory):
        zip_class = (LinuxZipFileWithPermissions if self._os_system_manager.get_os_name() == "linux" else zipfile.ZipFile)
        archive = zip_class(archive_file.file_path)
        self.__remove_file_dir_conflicts(archive.namelist(), to_directory)
        try:
            archive.extractall(to_directory)
        except Exception as e:
            if e.args[0] not in [26, 13] and e.args[1] not in [
                "Text file busy",
                "Permission denied",
            ]:
                raise e
            file_names = []
            for n in archive.namelist():
                if "/" not in n:
                    file_names.append(n)
                else:
                    file_path, file_name = n.rsplit("/", 1)
                    if not file_name:
                        continue
                    full_file_path = os.path.join(to_directory, file_path)
                    source = os.path.join(full_file_path, file_name)
                    destination = os.path.join(to_directory, file_name)
                    if not os.path.exists(source):
                        continue
                    os.replace(source, destination)
                    file_names.append(file_name)
            return sorted(file_names, key=lambda x: x.lower())
        return archive.namelist()

    def __remove_file_dir_conflicts(self, names, to_directory):
        """Remove stale files that conflict with directory entries in archive.

        Example: if cache contains `<to_directory>/operadriver` as a file, and
        archive now contains `operadriver/<binary>`, extraction would fail with
        NotADirectoryError.
        """
        top_level_dirs = {name.split("/", 1)[0] for name in names if "/" in name}
        for dir_name in top_level_dirs:
            path = os.path.join(to_directory, dir_name)
            if os.path.isfile(path):
                os.remove(path)

    def __extract_tar_file(self, archive_file, to_directory):
        try:
            tar = tarfile.open(archive_file.file_path, mode="r:gz")
        except tarfile.ReadError:
            tar = tarfile.open(archive_file.file_path, mode="r:bz2")
        members = tar.getmembers()
        # Python 3.13+ requires 'filter' to avoid deprecation warning
        if sys.version_info >= (3, 13):
            tar.extractall(to_directory, filter="fully_trusted")
        else:
            tar.extractall(to_directory)
        tar.close()
        return [x.name for x in members]
