"""Tests for TOML-based version and dependency update functions."""

from pathlib import Path
from textwrap import dedent

from crewai_devtools.cli import (
    _DEFAULT_WORKSPACE_PACKAGES,
    _pin_crewai_deps,
    _repin_crewai_install,
    update_pyproject_dependencies,
    update_pyproject_version,
    update_template_dependencies,
)


# --- update_pyproject_version ---


class TestUpdatePyprojectVersion:
    def test_updates_version(self, tmp_path: Path) -> None:
        pyproject = tmp_path / "pyproject.toml"
        pyproject.write_text(
            dedent("""\
            [project]
            name = "my-pkg"
            version = "1.0.0"
        """)
        )

        assert update_pyproject_version(pyproject, "2.0.0") is True
        assert 'version = "2.0.0"' in pyproject.read_text()

    def test_returns_false_when_already_current(self, tmp_path: Path) -> None:
        pyproject = tmp_path / "pyproject.toml"
        pyproject.write_text(
            dedent("""\
            [project]
            name = "my-pkg"
            version = "1.0.0"
        """)
        )

        assert update_pyproject_version(pyproject, "1.0.0") is False

    def test_returns_false_when_no_project_section(self, tmp_path: Path) -> None:
        pyproject = tmp_path / "pyproject.toml"
        pyproject.write_text("[tool.ruff]\nline-length = 88\n")

        assert update_pyproject_version(pyproject, "1.0.0") is False

    def test_returns_false_when_version_is_dynamic(self, tmp_path: Path) -> None:
        pyproject = tmp_path / "pyproject.toml"
        pyproject.write_text(
            dedent("""\
            [project]
            name = "my-pkg"
            dynamic = ["version"]
        """)
        )

        assert update_pyproject_version(pyproject, "1.0.0") is False
        assert 'version = "1.0.0"' not in pyproject.read_text()

    def test_returns_false_for_missing_file(self, tmp_path: Path) -> None:
        assert update_pyproject_version(tmp_path / "nope.toml", "1.0.0") is False

    def test_preserves_comments_and_formatting(self, tmp_path: Path) -> None:
        content = dedent("""\
            # This is important
            [project]
            name = "my-pkg"
            version = "1.0.0"  # current version
            description = "A package"
        """)
        pyproject = tmp_path / "pyproject.toml"
        pyproject.write_text(content)

        update_pyproject_version(pyproject, "2.0.0")
        result = pyproject.read_text()

        assert "# This is important" in result
        assert 'description = "A package"' in result


# --- _pin_crewai_deps ---


class TestPinCrewaiDeps:
    def test_pins_exact_version(self) -> None:
        content = dedent("""\
            [project]
            dependencies = [
                "crewai==1.0.0",
            ]
        """)
        result = _pin_crewai_deps(content, "2.0.0")
        assert '"crewai==2.0.0"' in result

    def test_pins_minimum_version(self) -> None:
        content = dedent("""\
            [project]
            dependencies = [
                "crewai>=1.0.0",
            ]
        """)
        result = _pin_crewai_deps(content, "2.0.0")
        assert '"crewai==2.0.0"' in result
        assert ">=" not in result

    def test_pins_with_tools_extra(self) -> None:
        content = dedent("""\
            [project]
            dependencies = [
                "crewai[tools]==1.0.0",
            ]
        """)
        result = _pin_crewai_deps(content, "2.0.0")
        assert '"crewai[tools]==2.0.0"' in result

    def test_leaves_unrelated_deps_alone(self) -> None:
        content = dedent("""\
            [project]
            dependencies = [
                "requests>=2.0",
                "crewai==1.0.0",
                "click~=8.1",
            ]
        """)
        result = _pin_crewai_deps(content, "2.0.0")
        assert '"requests>=2.0"' in result
        assert '"click~=8.1"' in result

    def test_handles_optional_dependencies(self) -> None:
        content = dedent("""\
            [project]
            dependencies = []

            [project.optional-dependencies]
            tools = [
                "crewai[tools]>=1.0.0",
            ]
        """)
        result = _pin_crewai_deps(content, "3.0.0")
        assert '"crewai[tools]==3.0.0"' in result

    def test_handles_multiple_crewai_entries(self) -> None:
        content = dedent("""\
            [project]
            dependencies = [
                "crewai==1.0.0",
                "crewai[tools]==1.0.0",
            ]
        """)
        result = _pin_crewai_deps(content, "2.0.0")
        assert '"crewai==2.0.0"' in result
        assert '"crewai[tools]==2.0.0"' in result

    def test_preserves_arbitrary_extras(self) -> None:
        content = dedent("""\
            [project]
            dependencies = [
                "crewai[a2a]==1.0.0",
            ]
        """)
        result = _pin_crewai_deps(content, "2.0.0")
        assert '"crewai[a2a]==2.0.0"' in result

    def test_no_deps_returns_unchanged(self) -> None:
        content = dedent("""\
            [project]
            name = "empty"
        """)
        result = _pin_crewai_deps(content, "2.0.0")
        assert "empty" in result

    def test_skips_crewai_without_version_specifier(self) -> None:
        content = dedent("""\
            [project]
            dependencies = [
                "crewai-tools~=1.0",
            ]
        """)
        result = _pin_crewai_deps(content, "2.0.0")
        assert '"crewai-tools~=1.0"' in result

    def test_skips_crewai_extras_without_pin(self) -> None:
        content = dedent("""\
            [project]
            dependencies = [
                "crewai[tools]",
            ]
        """)
        result = _pin_crewai_deps(content, "2.0.0")
        assert '"crewai[tools]"' in result
        assert "==" not in result


# --- _repin_crewai_install ---


class TestRepinCrewaiInstall:
    def test_repins_a2a_extra(self) -> None:
        result = _repin_crewai_install('uv pip install "crewai[a2a]==1.14.0"', "2.0.0")
        assert result == 'uv pip install "crewai[a2a]==2.0.0"'

    def test_repins_tools_extra(self) -> None:
        result = _repin_crewai_install('uv pip install "crewai[tools]==1.0.0"', "3.0.0")
        assert result == 'uv pip install "crewai[tools]==3.0.0"'

    def test_leaves_unrelated_commands_alone(self) -> None:
        cmd = "uv pip install requests"
        assert _repin_crewai_install(cmd, "2.0.0") == cmd

    def test_handles_multiple_pins(self) -> None:
        cmd = 'pip install "crewai[a2a]==1.0.0" "crewai[tools]==1.0.0"'
        result = _repin_crewai_install(cmd, "2.0.0")
        assert result == 'pip install "crewai[a2a]==2.0.0" "crewai[tools]==2.0.0"'

    def test_preserves_surrounding_text(self) -> None:
        cmd = 'echo hello && uv pip install "crewai[a2a]==1.14.0" && echo done'
        result = _repin_crewai_install(cmd, "2.0.0")
        assert (
            result == 'echo hello && uv pip install "crewai[a2a]==2.0.0" && echo done'
        )

    def test_no_version_specifier_unchanged(self) -> None:
        cmd = 'pip install "crewai[tools]>=1.0"'
        assert _repin_crewai_install(cmd, "2.0.0") == cmd


# --- update_pyproject_dependencies ---


class TestUpdatePyprojectDependencies:
    def test_default_packages_cover_all_workspace_members(self) -> None:
        """Every workspace member must be in the default rewrite list.

        Without this, a version bump silently leaves stale pins behind for any
        workspace package missing from the list (see incident with 1.14.5a5).
        """
        import tomlkit

        workspace_root = Path(__file__).resolve().parents[3]
        root_pyproject = (workspace_root / "pyproject.toml").read_text()

        members = tomlkit.parse(root_pyproject)["tool"]["uv"]["workspace"]["members"]
        expected = {
            tomlkit.parse((workspace_root / m / "pyproject.toml").read_text())[
                "project"
            ]["name"]
            for m in members
        }

        assert expected.issubset(set(_DEFAULT_WORKSPACE_PACKAGES))

    def test_rewrites_all_workspace_pins(self, tmp_path: Path) -> None:
        pyproject = tmp_path / "pyproject.toml"
        pyproject.write_text(
            dedent("""\
            [project]
            dependencies = [
                "crewai-core==1.0.0",
                "crewai-cli==1.0.0",
                "requests>=2.0",
            ]

            [project.optional-dependencies]
            tools = [
                "crewai-tools==1.0.0",
            ]
            files = [
                "crewai-files==1.0.0",
            ]
        """)
        )

        assert update_pyproject_dependencies(pyproject, "2.0.0") is True
        result = pyproject.read_text()
        assert '"crewai-core==2.0.0"' in result
        assert '"crewai-cli==2.0.0"' in result
        assert '"crewai-tools==2.0.0"' in result
        assert '"crewai-files==2.0.0"' in result
        assert '"requests>=2.0"' in result

    def test_skips_crewai_files_in_file_processing_extra(self, tmp_path: Path) -> None:
        pyproject = tmp_path / "pyproject.toml"
        pyproject.write_text(
            dedent("""\
            [project.optional-dependencies]
            file-processing = [
                "crewai-files==1.0.0",
            ]
            other = [
                "crewai-files==1.0.0",
            ]
        """)
        )

        update_pyproject_dependencies(pyproject, "2.0.0")
        result = pyproject.read_text()
        assert '"crewai-files==1.0.0"' in result
        assert '"crewai-files==2.0.0"' in result

    def test_leaves_bare_crewai_pin_alone(self, tmp_path: Path) -> None:
        """`crewai==` must not collide with `crewai-core==` etc."""
        pyproject = tmp_path / "pyproject.toml"
        pyproject.write_text(
            dedent("""\
            [project]
            dependencies = [
                "crewai==1.0.0",
                "crewai-core==1.0.0",
            ]
        """)
        )

        update_pyproject_dependencies(pyproject, "2.0.0")
        result = pyproject.read_text()
        assert '"crewai==2.0.0"' in result
        assert '"crewai-core==2.0.0"' in result


# --- update_template_dependencies ---


class TestUpdateTemplateDependencies:
    def test_updates_jinja_template(self, tmp_path: Path) -> None:
        """Template pyproject.toml files with Jinja placeholders should not break."""
        tpl = tmp_path / "crew" / "pyproject.toml"
        tpl.parent.mkdir()
        tpl.write_text(
            dedent("""\
            [project]
            name = "{{folder_name}}"
            version = "0.1.0"
            dependencies = [
                "crewai[tools]==1.14.0"
            ]

            [project.scripts]
            {{folder_name}} = "{{folder_name}}.main:run"
        """)
        )

        updated = update_template_dependencies(tmp_path, "2.0.0")

        assert len(updated) == 1
        content = tpl.read_text()
        assert '"crewai[tools]==2.0.0"' in content
        assert "{{folder_name}}" in content

    def test_updates_bare_crewai(self, tmp_path: Path) -> None:
        tpl = tmp_path / "pyproject.toml"
        tpl.write_text('dependencies = [\n    "crewai==1.0.0"\n]\n')

        updated = update_template_dependencies(tmp_path, "3.0.0")

        assert len(updated) == 1
        assert '"crewai==3.0.0"' in tpl.read_text()

    def test_skips_unrelated_deps(self, tmp_path: Path) -> None:
        tpl = tmp_path / "pyproject.toml"
        tpl.write_text('dependencies = [\n    "requests>=2.0"\n]\n')

        updated = update_template_dependencies(tmp_path, "2.0.0")

        assert len(updated) == 0
        assert '"requests>=2.0"' in tpl.read_text()
