from pathlib import Path

import pytest

import usethis._tool.impl.base.pytest
from usethis._config import usethis_config
from usethis._config_file import files_manager
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._integrations.ci.bitbucket.steps import add_placeholder_step_in_default
from usethis._python.version import PythonVersion
from usethis._test import change_cwd
from usethis._tool.impl.base.pytest import PytestTool
from usethis._types.backend import BackendEnum


class TestPytestTool:
    class TestDefaultCommand:
        def test_uv_backend_with_uv_lock(self, tmp_path: Path):
            # Arrange
            (tmp_path / "uv.lock").touch()

            # Act
            with change_cwd(tmp_path), files_manager():
                cmd = PytestTool().how_to_use_cmd()

            # Assert
            assert cmd == "uv run pytest"

        def test_uv_backend_without_uv_lock(self, tmp_path: Path):
            # Arrange - no uv.lock file

            # Act
            with change_cwd(tmp_path), files_manager():
                cmd = PytestTool().how_to_use_cmd()

            # Assert
            assert cmd == "pytest"

        def test_none_backend(self, tmp_path: Path):
            # Arrange

            # Act
            with (
                change_cwd(tmp_path),
                files_manager(),
                usethis_config.set(backend=BackendEnum.none),
            ):
                cmd = PytestTool().how_to_use_cmd()

            # Assert
            assert cmd == "pytest"

    class TestUpdateBitbucketSteps:
        def test_new_file(self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]):
            with change_cwd(uv_init_dir), files_manager():
                # Arrange
                PyprojectTOMLManager()[["project"]]["requires-python"] = ">=3.12,<3.14"
                add_placeholder_step_in_default(report_placeholder=False)
                (uv_init_dir / "pytest.ini").touch()

                # Act
                PytestTool().update_bitbucket_steps()

            # Assert
            assert (uv_init_dir / "bitbucket-pipelines.yml").exists()
            contents = (uv_init_dir / "bitbucket-pipelines.yml").read_text()
            assert (
                contents
                == """\
image: atlassian/default-image:3
definitions:
    caches:
        uv: ~/.cache/uv
    script_items:
      - &install-uv |
        curl -LsSf https://astral.sh/uv/install.sh | sh
        source $HOME/.local/bin/env
        export UV_LINK_MODE=copy
        uv --version
pipelines:
    default:
      - step:
            name: Test on 3.12
            caches:
              - uv
            script:
              - *install-uv
              - uv run --python 3.12 pytest -x --junitxml=test-reports/report.xml
      - step:
            name: Test on 3.13
            caches:
              - uv
            script:
              - *install-uv
              - uv run --python 3.13 pytest -x --junitxml=test-reports/report.xml
"""
            )

            out, err = capfd.readouterr()
            assert not err
            assert out == (
                "✔ Writing 'bitbucket-pipelines.yml'.\n"
                "✔ Adding cache 'uv' definition to 'bitbucket-pipelines.yml'.\n"
                "✔ Adding 'Test on 3.12' to default pipeline in 'bitbucket-pipelines.yml'.\n"
                "✔ Adding 'Test on 3.13' to default pipeline in 'bitbucket-pipelines.yml'.\n"
            )

        def test_remove_old_steps(
            self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]
        ):
            """Note this test also checks we don't add a cache when it's not needed."""
            # Arrange
            (uv_init_dir / "bitbucket-pipelines.yml").write_text(
                """\
image: atlassian/default-image:3
pipelines:
    default:
      - step:
            name: Test on 3.11
            script:
              - echo 'Hello, Python 3.11!'
      - step:
            name: Test on 3.12
            script:
              - echo 'Hello, Python 3.12!'
"""
            )
            (uv_init_dir / "pyproject.toml").write_text(
                """\
[project]
requires-python = ">=3.12,<3.13"
version = "0.1.0"
"""
            )
            (uv_init_dir / "pytest.ini").touch()

            # Act
            with (
                change_cwd(uv_init_dir),
                files_manager(),
                usethis_config.set(backend=BackendEnum.uv),
            ):
                PytestTool().update_bitbucket_steps()

            # Assert
            contents = (uv_init_dir / "bitbucket-pipelines.yml").read_text()
            assert (
                contents
                == """\
image: atlassian/default-image:3
pipelines:
    default:
      - step:
            name: Test on 3.12
            script:
              - echo 'Hello, Python 3.12!'
"""
            )
            out, err = capfd.readouterr()
            assert not err
            assert out == (
                "✔ Removing 'Test on 3.11' from default pipeline in 'bitbucket-pipelines.yml'.\n"
            )

        def test_no_requires_python(self, tmp_path: Path):
            # Arrange
            (tmp_path / "pyproject.toml").write_text(
                """\
[project]
name = "example"
version = "0.1.0"
"""
            )
            (tmp_path / "pytest.ini").touch()

            with (
                change_cwd(tmp_path),
                files_manager(),
                usethis_config.set(backend=BackendEnum.uv),
            ):
                add_placeholder_step_in_default(report_placeholder=False)

                # Act
                PytestTool().update_bitbucket_steps()

            # Assert
            contents = (tmp_path / "bitbucket-pipelines.yml").read_text()
            version = PythonVersion.from_interpreter()
            assert (
                contents
                == f"""\
image: atlassian/default-image:3
definitions:
    caches:
        uv: ~/.cache/uv
    script_items:
      - &install-uv |
        curl -LsSf https://astral.sh/uv/install.sh | sh
        source $HOME/.local/bin/env
        export UV_LINK_MODE=copy
        uv --version
pipelines:
    default:
      - step:
            name: Test on {version.to_short_string()}
            caches:
              - uv
            script:
              - *install-uv
              - uv run --python {version.to_short_string()} pytest -x --junitxml=test-reports/report.xml
"""
            )

        def test_no_backend(
            self,
            tmp_path: Path,
            capfd: pytest.CaptureFixture[str],
            monkeypatch: pytest.MonkeyPatch,
        ):
            # Arrange
            monkeypatch.setattr(
                usethis._tool.impl.base.pytest, "get_backend", lambda: BackendEnum.none
            )
            monkeypatch.setattr(
                "usethis._python.version.PythonVersion.from_interpreter",
                lambda: PythonVersion(major="3", minor="10", patch=None),
            )
            (tmp_path / "bitbucket-pipelines.yml").touch()
            (tmp_path / "pytest.ini").touch()
            (tmp_path / "pyproject.toml").write_text(
                """\
[project]
requires-python = ">=3.13,<3.14"
"""
            )

            # Act
            with change_cwd(tmp_path), files_manager():
                PytestTool().update_bitbucket_steps()

            # Assert
            out, err = capfd.readouterr()
            assert not err
            expected_output = """\
⚠ Current Python interpreter (3.10) is outside requires-python bounds \n(<3.14,>=3.13). Using lowest supported version (3.13).
✔ Adding 'Test on 3.13' to default pipeline in 'bitbucket-pipelines.yml'.
ℹ Consider installing 'uv' to readily manage test dependencies.
☐ Declare your test dependencies in 'bitbucket-pipelines.yml'.
ℹ Add test dependencies to this line: 'pip install pytest'
"""  # noqa: RUF001
            assert out == expected_output

    class TestRemoveBitbucketSteps:
        def test_no_file(self, uv_init_dir: Path):
            # Act
            with change_cwd(uv_init_dir):
                PytestTool().remove_bitbucket_steps()

            # Assert
            assert not (uv_init_dir / "bitbucket-pipelines.yml").exists()

        def test_dont_touch_if_no_pytest_steps(self, uv_init_dir: Path):
            # Arrange
            with change_cwd(uv_init_dir), files_manager():
                add_placeholder_step_in_default(report_placeholder=False)
                PytestTool().update_bitbucket_steps()
            contents = (uv_init_dir / "bitbucket-pipelines.yml").read_text()
            (uv_init_dir / "pytest.ini").touch()

            # Act
            with change_cwd(uv_init_dir), files_manager():
                PytestTool().remove_bitbucket_steps()

            # Assert
            assert (uv_init_dir / "bitbucket-pipelines.yml").exists()
            assert (uv_init_dir / "bitbucket-pipelines.yml").read_text() == contents

        def test_one_step(self, uv_init_dir: Path):
            # Arrange
            (uv_init_dir / "bitbucket-pipelines.yml").write_text(
                """\
image: atlassian/default-image:3
pipelines:
  default:
    - step:
        name: Test on 3.12
        script:
          - echo 'Hello, Python 3.12!'
"""
            )

            # Act
            with change_cwd(uv_init_dir), files_manager():
                PytestTool().remove_bitbucket_steps()

            # Assert
            contents = (uv_init_dir / "bitbucket-pipelines.yml").read_text()
            assert (
                contents
                == """\
image: atlassian/default-image:3
definitions:
    caches:
        uv: ~/.cache/uv
    script_items:
      - &install-uv |
        curl -LsSf https://astral.sh/uv/install.sh | sh
        source $HOME/.local/bin/env
        export UV_LINK_MODE=copy
        uv --version
pipelines:
    default:
      - step:
            name: Placeholder - add your own steps!
            caches:
              - uv
            script:
              - *install-uv
              - echo 'Hello, world!'
"""
            )

    class TestAddConfig:
        def test_empty_dir(self, tmp_path: Path):
            # Expect pytest.ini to be preferred

            # Act
            with change_cwd(tmp_path), files_manager():
                PytestTool().add_configs()

            # Assert
            assert (tmp_path / "pytest.ini").exists()
            assert not (tmp_path / "pyproject.toml").exists()

        def test_pyproject_toml_exists(self, tmp_path: Path):
            # Arrange
            (tmp_path / "pyproject.toml").touch()

            # Act
            with change_cwd(tmp_path), files_manager():
                PytestTool().add_configs()

            # Assert
            assert not (tmp_path / "pytest.ini").exists()
            assert (tmp_path / "pyproject.toml").exists()
