import itertools
import shutil
import tempfile
import zipfile
from pathlib import Path

from dev_tools.build.licensing.common import Dependency, LICENSE_ALIASES
from dev_tools.build.licensing.resources import licensing_resource_path


def _parse_lib(lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url):
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_uvloop(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name == "MIT"  # dual licensed; choose Apache over MIT license
    assert (dist_info_dir / "LICENSE-APACHE").is_file()
    license_name = "Apache 2"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_setuptools(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name in ("UNKNOWN", "MIT"), str(license_name)  # license metadata was not properly set-up
    license_name = "MIT"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_lxml(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name in ("BSD", "BSD-3-Clause"), str(license_name)  # license metadata may not be exact
    license_name = "BSD-3-Clause"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_attrs(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name == "MIT", str(license_name)
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_packaging(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name in (
        "Apache Software License, BSD License", "BSD-2-Clause or Apache-2.0", None
    ), str(license_name)  # choose Apache
    license_name = "Apache-2.0"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_importlib_metadata(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name == "Apache Software License", str(license_name)  # license metadata is not properly set-up
    license_name = "Apache-2.0"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_idna(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name == "BSD License", str(license_name)  # license metadata is not properly set-up
    license_name = "BSD-3-Clause"
    assert license_text is None  # missing from distribution package
    license_text = licensing_resource_path("idna_license").read_text(encoding="utf-8")  # copied from the project's repo
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_psutil(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name == "BSD-3-Clause", str(license_name)
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_orjson(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name == "Apache-2.0 OR MIT"  # choose Apache
    license_name = "Apache-2.0"
    assert license_text is None  # missing from distribution package
    license_text = licensing_resource_path("orjson_license_apache_2").read_text(
        encoding="utf-8"
    )  # copied from the project's repo
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_pep517(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name is None  # license metadata is not properly set-up
    license_name = "MIT"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_gcld3(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name is None, license_name  # license metadata is not properly set-up
    license_name = "Apache-2.0"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_pycryptodome(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name == "BSD, Public Domain"
    license_name = "BSD-2-Clause"  # simplify
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_pycparser(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name == "BSD"  # license metadata is not exact
    license_name = "BSD-3-Clause"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_pyzmq(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name == "LGPL+BSD"  # license metadata is not exact
    license_name = "BSD-4-Clause"
    assert license_text is None  # missing from distribution package
    license_text = licensing_resource_path("pyzmq_license_bsd").read_text(
        encoding="utf-8"
    )  # copied from the project's repo
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_pywin32(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name == "PSF"
    assert license_text is None
    license_text = (lib_path / "win32com/License.txt").read_text(encoding="utf-8")
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_clarabot_crypto(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name in ("UNKNOWN", "Other/Proprietary License"), license_name
    license_text = license_name = "Clarabot Limited Review License"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_numpy(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name in ("BSD", "BSD-3-Clause"), str(license_name)
    license_name = "BSD-3-Clause"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_passlib(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name == "BSD"
    license_name = "BSD-3-Clause"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_zxcvbn(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name == "MIT"
    assert license_text is None  # missing from distribution package
    license_text = licensing_resource_path("zxcvbn_license").read_text(
        encoding="utf-8"
    )  # copied from the project's repo
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_colorama(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name in (None, "BSD", "BSD License"), str(license_name)  # license metadata is not exact or missing
    license_name = "BSD-3-Clause"
    license_text = licensing_resource_path("colorama_license").read_text(
        encoding="utf-8"
    )  # copied from the project's repo
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_pillow(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name in ("HPND", "Historical Permission Notice and Disclaimer"), str(license_name)
    license_name = "BSD-3-Clause"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_babel(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name == "BSD"  # license metadata is not exact
    license_name = "BSD-3-Clause"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def _parse_lib_typing_extensions(
    lib_path: Path, dist_info_dir: Path, name, license_name, license_text, author, author_email, home_url
):
    assert license_name is None  # license metadata is not properly set-up since version 4.0
    license_name = "PSF"
    return Dependency(name, license_name, license_text, author, author_email, home_url)


def parse_dist_info_dependency(lib_path: Path, dist_info_dir: Path):
    if (dist_info_dir / "METADATA").is_file():
        meta = (dist_info_dir / "METADATA").read_text(encoding="utf-8")
    elif (dist_info_dir / "PKG-INFO").is_file():
        meta = (dist_info_dir / "PKG-INFO").read_text(encoding="utf-8")
    else:
        Exception(f"not meta {dist_info_dir}")
    dependency_init_arg_names = ["Name", "License", "Author", "Author-email", "Home-page"]
    dependency_init_arg_values = [None] * len(dependency_init_arg_names)
    lic_from_classifier = []
    for line in meta.replace("\r", "").split("\n"):
        for i, attr in enumerate(dependency_init_arg_names):
            if line.startswith("Classifier: "):
                parts = line.partition(": ")[-1].split(" :: ")
                if parts and parts[0] == "License":
                    lic_from_classifier.append(parts[-1])
                break
            elif line.startswith(f"{attr}: "):
                assert dependency_init_arg_values[i] is None
                dependency_init_arg_values[i] = line[len(attr) + 2 :].strip()
                break
        if all(dependency_init_arg_values):
            break
    assert dependency_init_arg_values[0], "%s %s" % (dist_info_dir, dependency_init_arg_values)
    if dependency_init_arg_values[1] is None and lic_from_classifier:
        dependency_init_arg_values[1] = ", ".join(sorted(lic_from_classifier))
    dependency_init_arg_values[1] = LICENSE_ALIASES.get(dependency_init_arg_values[1], dependency_init_arg_values[1])
    for dir_path, name in itertools.product(
        [dist_info_dir, dist_info_dir / "licenses"],
        [
            "LICENSE.txt",
            "LICENSE",
            "LICENSE.rst",
            "COPYING",
            "LICENSES.txt",
            "LICENSE-APACHE",
            "COPYING.BSD",
            "LICENSE-MIT",
        ]
    ):
        try:
            license_text = (dir_path / name).read_text(encoding="utf-8")
            break
        except FileNotFoundError:
            pass
    else:
        license_text = None
    dependency_init_arg_values.insert(2, license_text)
    parse_fn = globals().get(f"_parse_lib_{dependency_init_arg_values[0].lower().replace('-', '_')}", _parse_lib)
    return parse_fn(lib_path, dist_info_dir, *dependency_init_arg_values)


def get_dependencies_from_library_zip(lib_path):
    lib_zip_path = lib_path / "library.zip"
    deps = []
    d = tempfile.mkdtemp()
    try:
        with zipfile.ZipFile(lib_zip_path) as zf:
            zf.extractall(str(d))
        for item in Path(d).iterdir():
            if not item.is_dir():
                continue
            if item.name.endswith(".egg"):
                raise NotImplementedError()
            elif item.name.endswith(".dist-info"):
                deps.append(parse_dist_info_dependency(lib_path, item))
    finally:
        shutil.rmtree(d)
    return deps
