import os
import typing
from pathlib import Path
from urllib.parse import unquote
from zipfile import ZIP_DEFLATED, ZipFile


class M3UArchiveWriter:
    def __init__(
        self,
        m3u_paths: typing.List[Path],
        archive_name: str,
        destination: Path,
        ignore_invalid: bool = False,
    ) -> None:
        """Initialize an instance of M3UArchive.

        Args:
            m3u_paths (typing.List[Path]): A list of all M3U files
            paths.
            archive_name (str): The new archive name.
            destination (Path): The destination of the archive.
            ignore_invalid (bool, optional): Flag to ignore invalid
            media file paths. Defaults to True.
        """
        self.m3u_paths = m3u_paths
        self.zip_name = archive_name
        self.destination = destination
        self.ignore_invalid = ignore_invalid

        # A list representing all media file paths.
        self.file_paths: typing.List[Path] = []

        self.archive_path = self.destination.joinpath(f"{self.zip_name}.zip")

    def write_archive(self) -> ZipFile:
        """Archive a list of media files.

        Returns:
            ZipFile: The archive as a ZipFile object.
        """
        for m3u_path in self.m3u_paths:
            self.file_paths.extend(
                self.eval_m3u(m3u_path, ignore_invalid=self.ignore_invalid)
            )

        # Record the current directory.
        current_dir = os.getcwd()

        # Change into the expected destination directory.
        os.chdir(self.destination)
        with ZipFile(self.archive_path, "w") as z_f:

            # Change to the directory that the M3U file is expecting file
            # references to be from.
            os.chdir(m3u_path.parent)

            for file in self.file_paths:

                try:
                    # If the file is an absolute path, then get the relative
                    # path from the M3U file location to the media file.
                    if file.is_absolute():
                        file = Path(
                            os.path.relpath(
                                file.resolve(), m3u_path.parent.resolve()
                            )
                        )

                    z_f.write(file, compress_type=ZIP_DEFLATED)

                except FileNotFoundError:
                    if self.ignore_invalid is False:
                        raise
                    else:
                        continue

        # Finally, return to the current directory.
        os.chdir(current_dir)

        return ZipFile(self.archive_path)

    @staticmethod
    def eval_m3u(
        m3u_path: Path, ignore_invalid: bool = False
    ) -> typing.List[Path]:
        """Evaluate an M3U file for media files.

        Args:
            m3u_path (Path): The path to the M3U file.
            ignore_invalid (bool, optional): Flag to ignore invalid
            media file paths. Defaults to False.

        Raises:
            FileNotFoundError: Raised if either the M3U file or any
            of the mentioned paths cannot be found (if ignore_invalid
            is False).

        Returns:
            typing.List[Path]: A list of all media file paths.
        """
        if (m3u_path.is_file() is False) or (
            m3u_path.suffix not in valid_suffixes
        ):
            raise FileNotFoundError(f"{m3u_path}: cannot find M3U file")

        current_dir = os.getcwd()

        with open(m3u_path, "r") as f:

            # Change to the directory that the M3U file is expecting file
            # references to be from.
            os.chdir(m3u_path.parent)

            file_paths = []

            for line in f:
                line = unquote(line)  # Decode the media file.

                # Automatic M3U file creators usually place unnecessary
                # trailing space characters and should be removed.
                line = line.strip()

                # If the line is empty, skip it.
                if not line:
                    continue

                # If the line is a comment, skip it.
                if line[0] == "#":
                    continue

                # If the file cannot be found, then throw an exception if
                # skip_invalid is False.
                line_path = Path(line)

                if line_path.exists() is not True:
                    if ignore_invalid is False:
                        raise FileNotFoundError(
                            f"{line_path.name}: media file cannot be found"
                        )
                    else:
                        continue

                file_paths.append(line_path)

        os.chdir(current_dir)

        return file_paths


valid_suffixes = [".m3u", ".m3u8"]
