"""Tests for qpip.project_venv."""

from __future__ import annotations

import io
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch

from qpip.project_venv import (
    SHELL_LINUX,
    SHELL_MACOS,
    SHELL_WINDOWS_BASH,
    SHELL_WINDOWS_CMD,
    SHELL_WINDOWS_POWERSHELL,
    VenvUsageError,
    build_venv_command,
    detect_shell,
    run_activate_command,
    run_deactivate_command,
    run_venv_command,
)


class DetectShellTests(unittest.TestCase):
    def test_detects_linux_sessions(self) -> None:
        shell_kind = detect_shell(platform_system="Linux", os_name="posix")

        self.assertEqual(shell_kind, SHELL_LINUX)

    def test_detects_macos_sessions(self) -> None:
        shell_kind = detect_shell(platform_system="Darwin", os_name="posix")

        self.assertEqual(shell_kind, SHELL_MACOS)

    def test_detects_windows_powershell_from_parent_process(self) -> None:
        shell_kind = detect_shell(os_name="nt", parent_process_name="powershell.exe")

        self.assertEqual(shell_kind, SHELL_WINDOWS_POWERSHELL)

    def test_detects_windows_bash_from_parent_process(self) -> None:
        shell_kind = detect_shell(os_name="nt", parent_process_name="bash.exe")

        self.assertEqual(shell_kind, SHELL_WINDOWS_BASH)

    def test_falls_back_to_windows_cmd(self) -> None:
        shell_kind = detect_shell(env={}, os_name="nt", parent_process_name="")

        self.assertEqual(shell_kind, SHELL_WINDOWS_CMD)

    def test_invalid_shell_override_raises(self) -> None:
        with self.assertRaises(VenvUsageError):
            detect_shell(env={"QPIP_SHELL": "bad-shell"}, os_name="nt")


class RunVenvCommandTests(unittest.TestCase):
    def test_build_venv_command_uses_default_directory(self) -> None:
        command = build_venv_command(python_executable="python")

        self.assertEqual(command, ["python", "-m", "venv", ".venv"])

    def test_run_venv_command_invokes_python_module(self) -> None:
        buffer = io.StringIO()
        demo_path = Path("D:/tmp/demo")

        with patch("qpip.project_venv.subprocess.run") as mocked:
            mocked.return_value.returncode = 0

            exit_code = run_venv_command(
                ["venv"],
                cwd=demo_path,
                output=buffer,
                python_executable="python",
            )

        self.assertEqual(exit_code, 0)
        self.assertIn("python -m venv .venv", buffer.getvalue())
        mocked.assert_called_once_with(
            ["python", "-m", "venv", ".venv"],
            cwd=str(demo_path),
            check=False,
        )

    def test_run_venv_command_supports_dry_run(self) -> None:
        buffer = io.StringIO()

        exit_code = run_venv_command(
            ["venv"],
            cwd=Path("D:/tmp/demo"),
            output=buffer,
            dry_run=True,
            python_executable="python",
        )

        self.assertEqual(exit_code, 0)
        self.assertIn("python -m venv .venv", buffer.getvalue())


class RunActivateCommandTests(unittest.TestCase):
    def test_activate_outputs_linux_command(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            project_dir = Path(tmp_dir)
            activate_script = project_dir / ".venv" / "bin" / "activate"
            activate_script.parent.mkdir(parents=True)
            activate_script.write_text("", encoding="utf-8")
            buffer = io.StringIO()

            exit_code = run_activate_command(
                ["active"],
                cwd=project_dir,
                output=buffer,
                platform_system="Linux",
                os_name="posix",
            )

            self.assertEqual(exit_code, 0)
            self.assertEqual(buffer.getvalue().strip(), "source .venv/bin/activate")

    def test_activate_outputs_windows_powershell_command(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            project_dir = Path(tmp_dir)
            activate_script = project_dir / ".venv" / "Scripts" / "Activate.ps1"
            activate_script.parent.mkdir(parents=True)
            activate_script.write_text("", encoding="utf-8")
            buffer = io.StringIO()

            exit_code = run_activate_command(
                ["active"],
                cwd=project_dir,
                output=buffer,
                os_name="nt",
                parent_process_name="pwsh.exe",
            )

            self.assertEqual(exit_code, 0)
            self.assertEqual(buffer.getvalue().strip(), "& .\\.venv\\Scripts\\Activate.ps1")

    def test_activate_outputs_windows_bash_command(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            project_dir = Path(tmp_dir)
            activate_script = project_dir / ".venv" / "Scripts" / "activate"
            activate_script.parent.mkdir(parents=True)
            activate_script.write_text("", encoding="utf-8")
            buffer = io.StringIO()

            exit_code = run_activate_command(
                ["active"],
                cwd=project_dir,
                output=buffer,
                os_name="nt",
                parent_process_name="bash.exe",
            )

            self.assertEqual(exit_code, 0)
            self.assertEqual(buffer.getvalue().strip(), "source .venv/Scripts/activate")

    def test_activate_requires_existing_virtual_environment(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            with self.assertRaises(VenvUsageError):
                run_activate_command(
                    ["active"],
                    cwd=Path(tmp_dir),
                    output=io.StringIO(),
                    platform_system="Linux",
                    os_name="posix",
                )


class RunDeactivateCommandTests(unittest.TestCase):
    def test_deactivate_outputs_posix_command(self) -> None:
        buffer = io.StringIO()

        exit_code = run_deactivate_command(
            ["deactivate"],
            cwd=Path("D:/tmp/demo"),
            output=buffer,
            platform_system="Linux",
            os_name="posix",
        )

        self.assertEqual(exit_code, 0)
        self.assertEqual(
            buffer.getvalue().strip(),
            "if command -v deactivate >/dev/null 2>&1; then deactivate; fi",
        )

    def test_deactivate_outputs_powershell_command(self) -> None:
        buffer = io.StringIO()

        exit_code = run_deactivate_command(
            ["deactivate"],
            cwd=Path("D:/tmp/demo"),
            output=buffer,
            os_name="nt",
            parent_process_name="powershell.exe",
        )

        self.assertEqual(exit_code, 0)
        self.assertEqual(
            buffer.getvalue().strip(),
            "if (Get-Command deactivate -ErrorAction SilentlyContinue) { deactivate }",
        )


if __name__ == "__main__":
    unittest.main()
