"""Integration tests for Polytropos wheel builds."""

import ast
import os
import textwrap
import warnings
from pathlib import Path
from zipfile import ZipFile

import plumbum
import pytest

from polytropos.build import build_wheel
from tests.conftest import build_file_tree


def extract_manifest(wheel_dir: Path, wheel_name: str) -> dict:
    """Extract manifest dict from wheel."""
    wheel_path = wheel_dir / wheel_name
    with ZipFile(wheel_path, "r") as whl:
        # Find the first __manifest__.py in the wheel
        manifest_files = [
            name for name in whl.namelist() if name.endswith("/__manifest__.py")
        ]
        if not manifest_files:
            raise FileNotFoundError("No __manifest__.py found in wheel")
        manifest_path = manifest_files[0]
        manifest_content = whl.read(manifest_path).decode("utf-8")
        return ast.literal_eval(manifest_content)


def extract_wheel_dependencies(wheel_dir: Path, wheel_name: str) -> list[str]:
    """Extract Requires-Dist entries from wheel metadata."""
    wheel_path = wheel_dir / wheel_name
    with ZipFile(wheel_path, "r") as whl:
        metadata_files = [
            name for name in whl.namelist() if name.endswith(".dist-info/METADATA")
        ]
        if not metadata_files:
            raise FileNotFoundError("No METADATA found in wheel")
        metadata_content = whl.read(metadata_files[0]).decode("utf-8")
        requires_dist = [
            line.split(":", 1)[1].strip()
            for line in metadata_content.splitlines()
            if line.startswith("Requires-Dist:")
        ]
        return requires_dist


def extract_wheel_metadata(wheel_dir: Path, wheel_name: str) -> dict[str, str]:
    """Extract key entries from wheel metadata as a dict."""
    wheel_path = wheel_dir / wheel_name
    with ZipFile(wheel_path, "r") as whl:
        metadata_files = [
            name for name in whl.namelist() if name.endswith(".dist-info/METADATA")
        ]
        if not metadata_files:
            raise FileNotFoundError("No METADATA found in wheel")
        metadata_content = whl.read(metadata_files[0]).decode("utf-8")
        result = {}
        key = None
        for line in metadata_content.splitlines():
            if ":" in line and not line.startswith(" "):
                key, value = line.split(":", 1)
                result[key] = value.strip()
            elif key and line.startswith(" "):
                result[key] += "\n" + line.strip()
        return result


class TestBuildIntegration:
    """Integration tests for building Odoo modules."""

    def test_basic_build(self, tmp_path):
        """Test building a basic module."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert result == "odoo_addon_test_module-17.0.1.0.0-py3-none-any.whl"

            manifest = extract_manifest(wheel_dir, result)
            assert manifest == {
                "name": "Test Module",
                "version": "17.0.1.0.0",
                "depends": [],
            }

    def test_build_with_depends(self, tmp_path):
        """Test building a module with dependencies."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = ["base"]
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert result == "odoo_addon_test_module-17.0.1.0.0-py3-none-any.whl"

            manifest = extract_manifest(wheel_dir, result)
            assert manifest == {
                "name": "Test Module",
                "version": "17.0.1.0.0",
                "depends": ["base"],
            }

    def test_build_with_overrides(self, tmp_path):
        """Test building a module with version overrides."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = ["base"]

                        [[tool.polytropos.manifest]]
                        releases = ">=18"
                        application = true
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            expected_manifests = {
                "17.0": {
                    "name": "Test Module",
                    "version": "17.0.1.0.0",
                    "depends": ["base"],
                },
                "18.0": {
                    "name": "Test Module",
                    "version": "18.0.1.0.0",
                    "depends": ["base"],
                    "application": True,
                },
                "19.0": {
                    "name": "Test Module",
                    "version": "19.0.1.0.0",
                    "depends": ["base"],
                    "application": True,
                },
            }

            for release, expected_manifest in expected_manifests.items():
                result = build_wheel(str(wheel_dir), {"odoo_release": release})

                expected_version = f"{release.split('.')[0]}.0.1.0.0"
                assert (
                    result
                    == f"odoo_addon_test_module-{expected_version}-py3-none-any.whl"
                )

                manifest = extract_manifest(wheel_dir, result)
                assert manifest == expected_manifest

    def test_build_with_data_files(self, tmp_path):
        """Test building a module with data files."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = ["base"]
                        data = ["data/views.xml"]
                    """),
                    "__init__.py": "",
                    "data/views.xml": '<?xml version="1.0"?><odoo/>',
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert result == "odoo_addon_test_module-17.0.1.0.0-py3-none-any.whl"

            manifest = extract_manifest(wheel_dir, result)
            assert manifest == {
                "name": "Test Module",
                "version": "17.0.1.0.0",
                "depends": ["base"],
                "data": ["data/views.xml"],
            }

            wheel_path = wheel_dir / result
            with ZipFile(wheel_path, "r") as whl:
                names = whl.namelist()
                assert "odoo/addons/test_module/data/views.xml" in names

    def test_build_error_no_release(self, tmp_path):
        """Test that build fails without Odoo release."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            with pytest.raises(ValueError, match="Odoo release not specified"):
                build_wheel(str(wheel_dir), None)


class TestVersionBuilding:
    """Tests for version building logic."""

    @pytest.mark.parametrize(
        "module_version,release,expected",
        [
            ("1.0.0", "17.0", "17.0.1.0.0"),
            ("1.2.3", "18.0", "18.0.1.2.3"),
            ("2.1.0", "16.0", "16.0.2.1.0"),
            ("1.0.0", "19.0", "19.0.1.0.0"),
        ],
    )
    def test_version_building(self, tmp_path, module_version, release, expected):
        """Test that module version is combined with Odoo release correctly."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent(f"""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "{module_version}"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "{release}"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": release})

            assert result == f"odoo_addon_test_module-{expected}-py3-none-any.whl"

    @pytest.mark.parametrize(
        "module_version,release",
        [
            ("1.0", "17.0"),
            ("1", "17.0"),
            ("2.0", "18.0"),
        ],
    )
    def test_version_too_few_components_raises(self, tmp_path, module_version, release):
        """Test that module version with fewer than 3 components raises ValueError."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent(f"""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "{module_version}"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "{release}"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            with pytest.raises(ValueError, match="must have at least 3 components"):
                build_wheel(str(wheel_dir), {"odoo_release": release})


class TestPackageNameValidation:
    """Tests for package name validation."""

    def test_invalid_package_name_without_prefix(self, tmp_path):
        """Test that package name must start with odoo-addon-."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "my_module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            with pytest.raises(ValueError, match="Invalid package name"):
                build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

    def test_valid_package_name(self, tmp_path):
        """Test that valid package name works correctly."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-example-format"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = ["base"]
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert "example_format" in result
            manifest = extract_manifest(wheel_dir, result)
            assert manifest == {
                "name": "Example Format",
                "version": "17.0.1.0.0",
                "depends": ["base"],
            }

    def test_explicit_name_override(self, tmp_path):
        """Test that explicit name in manifest is used as module name."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-package-name"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        name = "Custom human-readable name"
                        depends = ["base"]
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert result == "odoo_addon_package_name-17.0.1.0.0-py3-none-any.whl"

            manifest = extract_manifest(wheel_dir, result)
            assert manifest == {
                "name": "Custom human-readable name",
                "version": "17.0.1.0.0",
                "depends": ["base"],
            }


class TestDefaultOdooRelease:
    """Tests for default_odoo_release fallback."""

    def test_default_release_used_when_no_config(self, tmp_path):
        """Test that default_odoo_release is used when no config_settings."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "18.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            old_env = os.environ.pop("ODOO_RELEASE", None)
            try:
                result = build_wheel(str(wheel_dir), None)
                assert "18.0" in result
            finally:
                if old_env is not None:
                    os.environ["ODOO_RELEASE"] = old_env

    def test_config_overrides_default_release(self, tmp_path):
        """Test that config_settings overrides default_odoo_release."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "18.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert result == "odoo_addon_test_module-17.0.1.0.0-py3-none-any.whl"


class TestWheelPythonDependencies:
    """Tests for Python package-level dependencies (install_requires) in wheels."""

    def test_external_python_deps_from_project_dependencies(self, tmp_path):
        """Test that non-odoo-addon deps from project.dependencies appear in wheel's Requires-Dist."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dependencies = [
                            "funcy>=1",
                            "requests>=2.0",
                        ]
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "18.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "18.0"})

            deps = extract_wheel_dependencies(wheel_dir, result)
            assert any("funcy" in d for d in deps), f"Expected funcy in deps: {deps}"
            assert any("requests" in d for d in deps), (
                f"Expected requests in deps: {deps}"
            )

    def test_odoo_addon_deps_from_project_dependencies(self, tmp_path):
        """Test that odoo-addon-* deps from project.dependencies appear in wheel's Requires-Dist."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dependencies = [
                            "odoo-addon-partner-fax",
                            "odoo-addon-sale",
                        ]
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "18.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "18.0"})

            deps = extract_wheel_dependencies(wheel_dir, result)
            # manifestoo_core converts addon deps to odoo-addon-{module_name}=={series}.*
            # Note: sale is an Odoo core addon, so it won't appear in Requires-Dist
            assert any("odoo-addon-partner_fax" in d for d in deps), (
                f"Expected partner-fax in deps: {deps}"
            )

    def test_mixed_dependencies_all_in_wheel(self, tmp_path):
        """Test that all dependency types appear in wheel's Requires-Dist."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dependencies = [
                            "odoo>=18.0",
                            "odoo-addon-partner-fax",
                            "funcy>=1",
                        ]
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "18.0"

                        [[tool.polytropos.manifest]]
                        depends = ["base"]
                        external_dependencies.python = ["cowsay>6"]

                        [[tool.polytropos.manifest]]
                        releases = ">=19"
                        external_dependencies.python = ["funcy>=2"]
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "18.0"})

            deps = extract_wheel_dependencies(wheel_dir, result)
            # Odoo series
            assert any("odoo==18.0" in d for d in deps), (
                f"Expected odoo==18.0.* in deps: {deps}"
            )
            # External Python deps from project.dependencies
            assert any("funcy>=1" in d for d in deps), (
                f"Expected funcy>=1 in deps: {deps}"
            )
            # Odoo addon from project.dependencies
            assert any("odoo-addon-partner_fax" in d for d in deps), (
                f"Expected partner-fax in deps: {deps}"
            )
            # External Python dep from manifest (18.0 release)
            assert any("cowsay" in d for d in deps), f"Expected cowsay in deps: {deps}"
            # Note: base is an Odoo core addon, so it won't appear in Requires-Dist

    def test_external_python_deps_combination_odoo_19_plus(self, tmp_path):
        """Test that external_dependencies.python lists are combined for Odoo 19+ overrides.

        Verifies that:
        - Base external_dependencies.python is preserved
        - Project dependencies are added
        - Version-specific external_dependencies.python are appended
        - Lists are combined (not replaced) as per deep merge behavior
        """
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dependencies = [
                            "funcy>=1",
                        ]
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "18.0"

                        [[tool.polytropos.manifest]]
                        external_dependencies.python = ["cowsay>6"]

                        [[tool.polytropos.manifest]]
                        releases = ">=19"
                        external_dependencies.python = ["cowsay>7", "copier>=9.0"]
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            # Test with Odoo 19.0 (should trigger the >=19 override)
            result = build_wheel(str(wheel_dir), {"odoo_release": "19.0"})

            deps = extract_wheel_dependencies(wheel_dir, result)

            # Verify all expected dependencies are present in the wheel's Requires-Dist
            # From project.dependencies
            assert any("funcy>=1" in d for d in deps), (
                f"Expected funcy>=1 in deps: {deps}"
            )
            # From base manifest external_dependencies.python
            assert any("cowsay>6" in d for d in deps), (
                f"Expected cowsay>6 in deps: {deps}"
            )
            # From Odoo 19+ override external_dependencies.python
            assert any("cowsay>7" in d for d in deps), (
                f"Expected cowsay>7 in deps: {deps}"
            )
            assert any("copier>=9.0" in d for d in deps), (
                f"Expected copier>=9.0 in deps: {deps}"
            )

            # Verify we have exactly the expected number of external Python dependencies
            # (Note: there may be additional dependencies like odoo series, etc.)
            python_deps = [
                d
                for d in deps
                if any(pkg in d for pkg in ["funcy", "cowsay", "copier"])
            ]
            assert len(python_deps) == 4, (
                f"Expected 4 python deps (funcy, cowsay>6, cowsay>7, copier), got: {python_deps}"
            )


class TestManifestConfigValidation:
    """Tests for validating [tool.polytropos.manifest] configuration."""

    def test_manifest_must_be_list(self, tmp_path):
        """Test that [tool.polytropos.manifest] must always be a list with [[double brackets]]."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "18.0"

                        [tool.polytropos.manifest]
                        depends = ["base"]
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            with pytest.raises(ValueError, match="must be a list"):
                build_wheel(str(wheel_dir), {"odoo_release": "18.0"})


class TestGitignoreHandling:
    """Tests for .gitignore handling in builds."""

    def test_gitignore_with_newlines(self, tmp_path):
        """Test that .gitignore with newlines is parsed correctly."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        data = ["data/example.xml"]
                    """),
                    "__init__.py": "",
                    ".gitignore": "__pycache__\n.cache\n.data\n.devenv\n.direnv\n",
                    ".cache/test.txt": "should be excluded",
                    "data/example.xml": '<?xml version="1.0"?><odoo/>',
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert result == "odoo_addon_test_module-17.0.1.0.0-py3-none-any.whl"

            wheel_path = wheel_dir / result
            with ZipFile(wheel_path, "r") as whl:
                names = whl.namelist()
                assert ".cache/test.txt" not in names
                assert any("data/example.xml" in n for n in names)


class TestSrcLayout:
    """Tests for src layout builds."""

    def test_basic_src_layout_build(self, tmp_path):
        """Test building a module with src layout."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "17.0"
                        src = "src"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "src/__init__.py": "",
                    "src/models/__init__.py": "",
                    "src/models/test.py": "pass",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert result == "odoo_addon_test_module-17.0.1.0.0-py3-none-any.whl"

            manifest = extract_manifest(wheel_dir, result)
            assert manifest == {
                "name": "Test Module",
                "version": "17.0.1.0.0",
                "depends": [],
            }

            wheel_path = wheel_dir / result
            with ZipFile(wheel_path, "r") as whl:
                names = whl.namelist()
                assert any("__init__.py" in n for n in names)
                assert any("models/__init__.py" in n for n in names)
                assert any("models/test.py" in n for n in names)
                # src/ should not appear in wheel paths
                assert not any("src/" in n for n in names)

    def test_src_layout_missing_dir_raises(self, tmp_path):
        """Test that src layout with missing directory raises FileNotFoundError."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "17.0"
                        src = "nonexistent"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            with pytest.raises(FileNotFoundError, match="Source directory not found"):
                build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

    def test_src_layout_with_data_files(self, tmp_path):
        """Test building a src layout module with data files."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "18.0"
                        src = "src"

                        [[tool.polytropos.manifest]]
                        depends = ["base"]
                        data = ["data/views.xml"]
                    """),
                    "src/__init__.py": "",
                    "src/data/views.xml": '<?xml version="1.0"?><odoo/>',
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "18.0"})

            assert result == "odoo_addon_test_module-18.0.1.0.0-py3-none-any.whl"

            manifest = extract_manifest(wheel_dir, result)
            assert manifest == {
                "name": "Test Module",
                "version": "18.0.1.0.0",
                "depends": ["base"],
                "data": ["data/views.xml"],
            }

            wheel_path = wheel_dir / result
            with ZipFile(wheel_path, "r") as whl:
                names = whl.namelist()
                assert any("data/views.xml" in n for n in names)


class TestDynamicVersionWarning:
    """Tests for dynamic=['version'] warning."""

    def test_warning_emitted_when_dynamic_version_not_set(self, tmp_path):
        """Test that warning is emitted when version is not dynamic."""


class TestReadmeValidation:
    """Tests for readme file validation in wheel builds."""

    def test_readme_included_in_metadata(self, tmp_path):
        """Test that readme specified in pyproject is included in wheel METADATA."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]
                        readme = "README.md"
                        readme-content-type = "text/markdown"

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                    "README.md": "# Test Module\n\nThis is a test module.",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert result == "odoo_addon_test_module-17.0.1.0.0-py3-none-any.whl"

            metadata = extract_wheel_metadata(wheel_dir, result)
            assert "Description-Content-Type" in metadata
            assert metadata["Description-Content-Type"] == "text/markdown"

    def test_no_warning_when_dynamic_version_is_set(self, tmp_path):
        """Test that no warning is emitted when version is dynamic."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            with warnings.catch_warnings():
                warnings.simplefilter("error", UserWarning)
                result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert result == "odoo_addon_test_module-17.0.1.0.0-py3-none-any.whl"

    def test_warning_emitted_in_build_sdist(self, tmp_path):
        """Test that warning is emitted in build_sdist when version is not dynamic."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            sdist_dir = tmp_path / "dist"
            sdist_dir.mkdir()

            with pytest.warns(
                UserWarning, match="version is defined.*but not listed in dynamic"
            ):
                from polytropos.build.pep517 import build_sdist

                build_sdist(str(sdist_dir), None)

    def test_warning_emitted_in_build_editable(self, tmp_path):
        """Test that warning is emitted in build_editable when version is not dynamic."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]
                        readme = "README.rst"

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                    "README.rst": "Test Module\n\nThis is a test module.",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert result == "odoo_addon_test_module-17.0.1.0.0-py3-none-any.whl"

            metadata = extract_wheel_metadata(wheel_dir, result)
            assert metadata["Description-Content-Type"] == "text/x-rst"

    def test_readme_file_missing_raises(self, tmp_path):
        """Test that build fails when readme is specified but file doesn't exist."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]
                        readme = "README.md"

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            with pytest.raises(ValueError, match="Description content not found"):
                build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

    def test_readme_md_auto_detects_markdown(self, tmp_path):
        """Test that .md file extension auto-detects text/markdown content type."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]
                        readme = "README.md"

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                    "README.md": "# Test Module\n\nThis is a test module.",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert result == "odoo_addon_test_module-17.0.1.0.0-py3-none-any.whl"

            metadata = extract_wheel_metadata(wheel_dir, result)
            assert "Description-Content-Type" in metadata
            assert metadata["Description-Content-Type"] == "text/markdown"

    def test_readme_rst_auto_detects_rst(self, tmp_path):
        """Test that .rst file extension auto-detects text/x-rst content type."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]
                        readme = "README.rst"

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                    "README.rst": "Test Module\n\nThis is a test module.",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert result == "odoo_addon_test_module-17.0.1.0.0-py3-none-any.whl"

            metadata = extract_wheel_metadata(wheel_dir, result)
            assert "Description-Content-Type" in metadata
            assert metadata["Description-Content-Type"] == "text/x-rst"

    def test_readme_txt_defaults_to_plain_text(self, tmp_path):
        """Test that .txt file extension defaults to text/plain content type."""
        with plumbum.local.cwd(tmp_path):
            build_file_tree(
                {
                    "pyproject.toml": textwrap.dedent("""\
                        [build-system]
                        requires = ["polytropos[build]"]
                        build-backend = "polytropos.build"

                        [project]
                        name = "odoo-addon-test-module"
                        version = "1.0.0"
                        requires-python = ">=3.11"
                        dynamic = ["version"]
                        readme = "README.txt"

                        [tool.polytropos]
                        default_odoo_release = "17.0"

                        [[tool.polytropos.manifest]]
                        depends = []
                    """),
                    "__init__.py": "",
                    "README.txt": "Test Module\n\nThis is a test module.",
                },
                base=tmp_path,
            )

            wheel_dir = tmp_path / "dist"
            wheel_dir.mkdir()

            result = build_wheel(str(wheel_dir), {"odoo_release": "17.0"})

            assert result == "odoo_addon_test_module-17.0.1.0.0-py3-none-any.whl"

            metadata = extract_wheel_metadata(wheel_dir, result)
            assert "Description-Content-Type" in metadata
            assert metadata["Description-Content-Type"] == "text/plain"
