import importlib
import subprocess
import sys
import tarfile
import zipfile

import pytest

import edsnlp
from edsnlp.package import package


def test_blank_package(nlp, tmp_path):
    # Missing metadata makes poetry fail due to missing author / description
    if not isinstance(nlp, edsnlp.Pipeline):
        pytest.skip("Only running for edsnlp.Pipeline")

    package(
        pipeline=nlp,
        root_dir=tmp_path,
        name="test-model-fail",
        metadata={},
        project_type="poetry",
    )

    nlp.package(
        root_dir=tmp_path,
        name="test-model",
        metadata={
            "description": "A test model",
            "authors": "Test Author <test.author@mail.com>",
        },
        project_type="poetry",
        distributions=["wheel"],
    )
    assert (tmp_path / "dist").is_dir()
    assert (tmp_path / "dist" / "test_model-0.1.0-py3-none-any.whl").is_file()
    assert not (tmp_path / "dist" / "test_model-0.1.0.tar.gz").is_file()


@pytest.mark.parametrize("package_name", ["my-test-model", None])
@pytest.mark.parametrize("manager", ["poetry", "setuptools"])
def test_package_with_files(nlp, tmp_path, package_name, manager):
    if not isinstance(nlp, edsnlp.Pipeline):
        pytest.skip("Only running for edsnlp.Pipeline")

    nlp.to_disk(tmp_path / "model", exclude=set())

    ((tmp_path / "test_model").mkdir(parents=True))
    (tmp_path / "test_model" / "__init__.py").write_text('print("Hello World!")\n')
    (tmp_path / "test_model" / "empty_folder").mkdir()
    (tmp_path / "README.md").write_text(
        """\
<!-- INSERT -->
# Test Model
"""
    )
    if manager == "poetry":
        (tmp_path / "pyproject.toml").write_text(
            """\
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "test-model"
version = "0.0.0"
description = "A test model"
authors = ["Test Author <test.author@mail.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = ">=3.7"
build = "*"  # sample light package to install
"""
        )
    elif manager == "setuptools":
        (tmp_path / "pyproject.toml").write_text(
            """\
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "test-model"
version = "0.0.0"
description = "A test model"
authors = [
    {name = "Test Author", email = "test.author@mail.com"}
]
readme = "README.md"
requires-python = ">=3.7"

dependencies = [
    "build"
]
"""
        )
    package(
        name=package_name,
        pipeline=tmp_path / "model",
        root_dir=tmp_path,
        check_dependencies=False,
        version="0.1.0",
        distributions=None,
        metadata={
            "description": "A new description",
            "authors": "Test Author <test.author@mail.com>",
        },
        readme_replacements={
            "<!-- INSERT -->": "Replaced !",
        },
    )

    module_name = "test_model" if package_name is None else "my_test_model"

    assert (tmp_path / "dist").is_dir()
    assert (tmp_path / "dist" / f"{module_name}-0.1.0.tar.gz").is_file()
    assert (tmp_path / "dist" / f"{module_name}-0.1.0-py3-none-any.whl").is_file()
    assert (tmp_path / "pyproject.toml").is_file()

    with zipfile.ZipFile(
        tmp_path / "dist" / f"{module_name}-0.1.0-py3-none-any.whl"
    ) as zf:
        # check files
        assert set(zf.namelist()) == {
            f"{module_name}-0.1.0.dist-info/METADATA",
            f"{module_name}-0.1.0.dist-info/RECORD",
            f"{module_name}-0.1.0.dist-info/WHEEL",
            f"{module_name}/__init__.py",
            f"{module_name}/artifacts/config.cfg",
            f"{module_name}/artifacts/meta.json",
            f"{module_name}/artifacts/tokenizer",
            "test_model/__init__.py",
        }
        # check description
        with zf.open(f"{module_name}-0.1.0.dist-info/METADATA") as f:
            assert b"A new description" in f.read()

    with tarfile.open(tmp_path / "dist" / f"{module_name}-0.1.0.tar.gz") as tf:
        # check files
        assert set(tf.getnames()) == {
            f"{module_name}-0.1.0/PKG-INFO",
            f"{module_name}-0.1.0/README.md",
            f"{module_name}-0.1.0/artifacts/config.cfg",
            f"{module_name}-0.1.0/artifacts/meta.json",
            f"{module_name}-0.1.0/artifacts/tokenizer",
            f"{module_name}-0.1.0/{module_name}/__init__.py",
            f"{module_name}-0.1.0/pyproject.toml",
            f"{module_name}-0.1.0/test_model/__init__.py",
        }
        # check description
        with tf.extractfile(f"{module_name}-0.1.0/PKG-INFO") as f:
            assert b"A new description" in f.read()

        with tf.extractfile(f"{module_name}-0.1.0/README.md") as f:
            assert b"Replaced !" in f.read()

    # pip install the whl file
    (tmp_path / "site-packages").mkdir(exist_ok=True)
    subprocess.check_output(
        [
            sys.executable,
            "-m",
            "pip",
            "install",
            "-vvv",
            "--target",
            str(tmp_path / "site-packages"),
            str(tmp_path / "dist" / f"{module_name}-0.1.0-py3-none-any.whl"),
        ],
        stderr=subprocess.STDOUT,
    )

    site_packages = tmp_path / "site-packages"
    sys.path.insert(0, str(site_packages))
    # check site-package files
    files = {str(f.relative_to(site_packages)) for f in set(site_packages.rglob("*"))}
    assert files >= {
        f"{module_name}/artifacts",
        f"{module_name}/artifacts/config.cfg",
        f"{module_name}/artifacts/meta.json",
        f"{module_name}/artifacts/tokenizer",
    }

    module = importlib.import_module(module_name)

    with open(module.__file__) as f:
        assert f.read() == (
            ('print("Hello World!")\n' if package_name is None else "")
            + """
# -----------------------------------------
# This section was autogenerated by edsnlp
# -----------------------------------------

import edsnlp
from pathlib import Path
from typing import Optional, Dict, Any

__version__ = '0.1.0'

def load(
    overrides: Optional[Dict[str, Any]] = None,
) -> edsnlp.Pipeline:
    path_outside = Path(__file__).parent / "../artifacts"
    path_inside = Path(__file__).parent / "artifacts"
    path = path_inside if path_inside.exists() else path_outside
    model = edsnlp.load(path, overrides=overrides)
    return model
"""
        )
    module.load()
    edsnlp.load(module_name)
