"""Tests for the mld CLI (mld build command)."""

import json
import zipfile
from pathlib import Path
from unittest.mock import patch

import pytest

from mld_sdk.cli import main, _build_manifest, _read_pyproject


@pytest.fixture
def plugin_project(tmp_path: Path) -> Path:
    """Create a minimal plugin project directory."""
    project = tmp_path / "my-plugin"
    project.mkdir()
    pyproject = project / "pyproject.toml"
    pyproject.write_text(
        '[project]\n'
        'name = "my-plugin"\n'
        'version = "0.1.0"\n'
        'description = "A test plugin"\n'
        'dependencies = ["fastapi"]\n'
        "\n"
        "[build-system]\n"
        'requires = ["hatchling"]\n'
        'build-backend = "hatchling.build"\n'
    )
    # Create a minimal source package so uv build could work
    src = project / "src" / "my_plugin"
    src.mkdir(parents=True)
    (src / "__init__.py").write_text("")
    return project


class TestBuildManifest:

    def test_basic_manifest(self):
        """Should produce a valid manifest dict."""
        project = {"name": "my-plugin", "version": "0.1.0", "description": "Test"}
        manifest = _build_manifest(project, "my_plugin-0.1.0-py3-none-any.whl", [])

        assert manifest["format_version"] == 1
        assert manifest["plugin"]["name"] == "my-plugin"
        assert manifest["plugin"]["version"] == "0.1.0"
        assert manifest["wheels"]["main"] == "my_plugin-0.1.0-py3-none-any.whl"
        assert manifest["wheels"]["dependencies"] == []

    def test_manifest_with_deps(self):
        """Should include dependency wheels in manifest."""
        project = {"name": "p", "version": "1.0"}
        deps = ["dep1-1.0-py3-none-any.whl", "dep2-2.0-py3-none-any.whl"]
        manifest = _build_manifest(project, "p-1.0-py3-none-any.whl", deps)

        assert manifest["wheels"]["dependencies"] == deps

    def test_manifest_default_requires_mld(self):
        """Should default requires_mld to >=0.1.0."""
        project = {"name": "p", "version": "1.0"}
        manifest = _build_manifest(project, "p-1.0.whl", [])
        assert manifest["plugin"]["requires_mld"] == ">=0.1.0"


class TestReadPyproject:

    def test_reads_project_table(self, plugin_project):
        """Should parse name and version from pyproject.toml."""
        project = _read_pyproject(plugin_project)
        assert project["name"] == "my-plugin"
        assert project["version"] == "0.1.0"

    def test_missing_pyproject(self, tmp_path):
        """Should exit when pyproject.toml is missing."""
        with pytest.raises(SystemExit):
            _read_pyproject(tmp_path)

    def test_missing_project_table(self, tmp_path):
        """Should exit when [project] table is missing."""
        (tmp_path / "pyproject.toml").write_text("[build-system]\n")
        with pytest.raises(SystemExit):
            _read_pyproject(tmp_path)


class TestMldBuildCommand:

    def test_build_produces_mld_file(self, plugin_project, tmp_path):
        """Should create a valid .mld archive with manifest + wheel."""
        output_dir = tmp_path / "output"
        fake_whl = tmp_path / "build" / "my_plugin-0.1.0-py3-none-any.whl"
        fake_whl.parent.mkdir(parents=True)
        with zipfile.ZipFile(fake_whl, "w") as zf:
            zf.writestr("my_plugin/__init__.py", "")

        with patch("mld_sdk.cli._build_wheel", return_value=fake_whl):
            main(["build", str(plugin_project), "--output-dir", str(output_dir)])

        mld_file = output_dir / "my-plugin-0.1.0.mld"
        assert mld_file.exists()

        with zipfile.ZipFile(mld_file, "r") as zf:
            names = zf.namelist()
            assert "manifest.json" in names
            assert "my_plugin-0.1.0-py3-none-any.whl" in names

            manifest = json.loads(zf.read("manifest.json"))
            assert manifest["format_version"] == 1
            assert manifest["plugin"]["name"] == "my-plugin"
            assert manifest["wheels"]["main"] == "my_plugin-0.1.0-py3-none-any.whl"

    def test_build_with_vendor_deps(self, plugin_project, tmp_path):
        """Should include dependency wheels when --vendor-deps is passed."""
        output_dir = tmp_path / "output"
        fake_whl = tmp_path / "build" / "my_plugin-0.1.0-py3-none-any.whl"
        fake_whl.parent.mkdir(parents=True)
        with zipfile.ZipFile(fake_whl, "w") as zf:
            zf.writestr("my_plugin/__init__.py", "")

        dep_whl = tmp_path / "dep" / "fastapi-0.109.0-py3-none-any.whl"
        dep_whl.parent.mkdir(parents=True)
        with zipfile.ZipFile(dep_whl, "w") as zf:
            zf.writestr("fastapi/__init__.py", "")

        def fake_download(project_dir, dest):
            # Copy dep wheel into dest
            import shutil
            shutil.copy(dep_whl, dest / dep_whl.name)
            return [dest / dep_whl.name]

        with (
            patch("mld_sdk.cli._build_wheel", return_value=fake_whl),
            patch("mld_sdk.cli._download_deps", side_effect=fake_download),
        ):
            main([
                "build", str(plugin_project),
                "--vendor-deps",
                "--output-dir", str(output_dir),
            ])

        mld_file = output_dir / "my-plugin-0.1.0.mld"
        with zipfile.ZipFile(mld_file, "r") as zf:
            names = zf.namelist()
            assert "fastapi-0.109.0-py3-none-any.whl" in names
            manifest = json.loads(zf.read("manifest.json"))
            assert "fastapi-0.109.0-py3-none-any.whl" in manifest["wheels"]["dependencies"]

    def test_no_command_shows_help(self, capsys):
        """Should exit with help when no subcommand is given."""
        with pytest.raises(SystemExit):
            main([])
