import codecs
import logging
import zipfile
from pathlib import Path, PurePosixPath
from typing import IO, Union

from .parser import ENTRY_POINT, MANIFEST_FILE_NAME, IParser

ARCHIVE_EXTENSION = "axp"
_ARCHIVE_SUFFIX = "." + ARCHIVE_EXTENSION


def _is_root_content(path: str) -> bool:
    return (path.endswith("/") and path.count("/") == 1) or "/" not in path


def _to_text_fileobject(fileobject_from_zip: IO[bytes]) -> codecs.StreamReader:
    return codecs.getreader("utf-8")(fileobject_from_zip)


class PackageParser(IParser):
    def parse(self, location_path: Union[str, Path]) -> dict:
        with zipfile.ZipFile(location_path) as archive:
            return self.read_archive(archive)

    def read_archive(self, archive: zipfile.ZipFile) -> dict:

        root_content = set(filter(_is_root_content, archive.namelist()))
        if len(root_content) != 1 or not next(iter(root_content)).endswith("/"):
            raise RuntimeError("Archive must contain one root directory")
        plugin_dir = PurePosixPath(next(iter(root_content)))
        try:
            entry_point = plugin_dir / ENTRY_POINT
            _ = archive.getinfo(str(entry_point))
        except KeyError:
            raise RuntimeError(f"Module must contain an entry point: {ENTRY_POINT}")
        try:
            manifest_path = plugin_dir / MANIFEST_FILE_NAME
            manifest_path_info = archive.getinfo(str(manifest_path))
        except KeyError:
            raise RuntimeError("Module must contain a manifest file")
        plugin_id = plugin_dir.name
        with archive.open(manifest_path_info) as manifest_file:
            text_fileobject = _to_text_fileobject(manifest_file)
            result = self.read_config(text_fileobject)
        result["id"] = plugin_id
        return result

    @classmethod
    def _get_id(cls, archive: zipfile.ZipFile) -> str:

        root_content = set(filter(_is_root_content, archive.namelist()))
        if len(root_content) != 1 or not next(iter(root_content)).endswith("/"):
            raise RuntimeError("Archive must contain one root directory")
        plugin_dir = PurePosixPath(next(iter(root_content)))
        return plugin_dir.name

    @staticmethod
    def supports(location_path: Union[str, Path]) -> bool:
        if isinstance(location_path, Path):
            return location_path.suffix == _ARCHIVE_SUFFIX
        return location_path.endswith(_ARCHIVE_SUFFIX)

    @staticmethod
    def install(package_file: str, destination: Path) -> None:
        module_destination = destination / "modules"
        if not module_destination.is_dir():
            raise RuntimeError("Module destination path doesn't exist or isn't a dir.")
        with zipfile.ZipFile(package_file, "r") as package:
            # extact package
            package.extractall(module_destination)
            module_id = PackageParser._get_id(package)
            module_rootdir = module_destination / module_id
            logging.info(f"Package installed: {module_rootdir}")
