"""Tests for qpip.cli."""

from __future__ import annotations

import io
import unittest
from contextlib import redirect_stdout
from pathlib import Path
from unittest.mock import patch

from qpip.cli import CLIUsageError, HELP_TEXT, main, parse_cli_args


class ParseCliArgsTests(unittest.TestCase):
    def test_supports_positional_mirror(self) -> None:
        options, warning = parse_cli_args(["tsinghua", "install", "numpy"])

        self.assertIsNone(warning)
        self.assertEqual(options.mirror, "tsinghua")
        self.assertEqual(options.pip_args, ("install", "numpy"))

    def test_supports_named_mirror(self) -> None:
        options, _ = parse_cli_args(["--mirror", "douban", "install", "pandas"])

        self.assertEqual(options.mirror, "douban")
        self.assertEqual(options.pip_args, ("install", "pandas"))

    def test_supports_pip_alias_as_positional_mirror(self) -> None:
        options, warning = parse_cli_args(["pip", "install", "numpy"])

        self.assertIsNone(warning)
        self.assertEqual(options.mirror, "pypi")
        self.assertEqual(options.pip_args, ("install", "numpy"))

    def test_supports_positional_mirror_after_qpip_flags(self) -> None:
        options, warning = parse_cli_args(["--dry-run", "pip", "install", "numpy"])

        self.assertIsNone(warning)
        self.assertTrue(options.dry_run)
        self.assertEqual(options.mirror, "pypi")
        self.assertEqual(options.pip_args, ("install", "numpy"))

    def test_respects_separator_for_pip_options(self) -> None:
        options, _ = parse_cli_args(["--", "--version"])

        self.assertEqual(options.pip_args, ("--version",))

    def test_detects_conflicting_mirrors(self) -> None:
        with self.assertRaises(CLIUsageError):
            parse_cli_args(["ali", "--mirror", "douban", "install", "requests"])

    def test_recognizes_qpip_flags_before_pip_args(self) -> None:
        options, _ = parse_cli_args(["--dry-run", "--list-mirrors"])

        self.assertTrue(options.dry_run)
        self.assertTrue(options.list_mirrors)
        self.assertEqual(options.pip_args, ())

    def test_help_text_covers_current_commands(self) -> None:
        self.assertIn("使用更快的软件包镜像运行 pip", HELP_TEXT)
        self.assertIn("用法:", HELP_TEXT)
        self.assertIn("说明:", HELP_TEXT)
        self.assertIn("qpip pip install numpy", HELP_TEXT)
        self.assertIn("qpip i requests", HELP_TEXT)
        self.assertIn("qpip update requests", HELP_TEXT)
        self.assertIn("qpip up requests", HELP_TEXT)
        self.assertIn("qpip uninstall requests", HELP_TEXT)
        self.assertIn("qpip un requests", HELP_TEXT)
        self.assertIn("qpip init", HELP_TEXT)
        self.assertIn("qpip run test", HELP_TEXT)
        self.assertIn("qpip venv", HELP_TEXT)
        self.assertIn("qpip active", HELP_TEXT)
        self.assertIn("qpip deactivate", HELP_TEXT)
        self.assertNotIn("qpip --dry-run install fastapi", HELP_TEXT)

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

        with redirect_stdout(buffer):
            exit_code = main(["--dry-run", "i", "requests"])

        self.assertEqual(exit_code, 0)
        self.assertIn("-m pip install requests", buffer.getvalue())

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

        with redirect_stdout(buffer):
            exit_code = main(["--dry-run", "up", "requests"])

        self.assertEqual(exit_code, 0)
        self.assertIn("-m pip install --upgrade requests", buffer.getvalue())

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

        with redirect_stdout(buffer):
            exit_code = main(["--dry-run", "un", "requests"])

        self.assertEqual(exit_code, 0)
        self.assertIn("-m pip uninstall requests", buffer.getvalue())

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

        with redirect_stdout(buffer):
            exit_code = main(["--dry-run", "pip", "install", "numpy"])

        self.assertEqual(exit_code, 0)
        self.assertIn("-m pip install numpy", buffer.getvalue())
        self.assertIn("https://pypi.org/simple/", buffer.getvalue())

    def test_main_dispatches_init_command(self) -> None:
        demo_path = Path("D:/tmp/demo")

        with patch("qpip.cli.Path.cwd", return_value=demo_path), patch(
            "qpip.cli.run_init_command",
            return_value=0,
        ) as mocked:
            exit_code = main(["init", "-y"])

        self.assertEqual(exit_code, 0)
        mocked.assert_called_once_with(("init", "-y"), cwd=demo_path, dry_run=False)

    def test_main_dispatches_run_command(self) -> None:
        demo_path = Path("D:/tmp/demo")

        with patch("qpip.cli.Path.cwd", return_value=demo_path), patch(
            "qpip.cli.run_project_command",
            return_value=0,
        ) as mocked:
            exit_code = main(["--dry-run", "run", "test"])

        self.assertEqual(exit_code, 0)
        mocked.assert_called_once_with(("run", "test"), cwd=demo_path, dry_run=True)

    def test_main_dispatches_venv_command(self) -> None:
        demo_path = Path("D:/tmp/demo")

        with patch("qpip.cli.Path.cwd", return_value=demo_path), patch(
            "qpip.cli.run_venv_command",
            return_value=0,
        ) as mocked:
            exit_code = main(["venv"])

        self.assertEqual(exit_code, 0)
        mocked.assert_called_once_with(("venv",), cwd=demo_path, dry_run=False)

    def test_main_dispatches_active_command(self) -> None:
        demo_path = Path("D:/tmp/demo")

        with patch("qpip.cli.Path.cwd", return_value=demo_path), patch(
            "qpip.cli.run_activate_command",
            return_value=0,
        ) as mocked:
            exit_code = main(["active"])

        self.assertEqual(exit_code, 0)
        mocked.assert_called_once_with(("active",), cwd=demo_path)

    def test_main_dispatches_deactivate_command(self) -> None:
        demo_path = Path("D:/tmp/demo")

        with patch("qpip.cli.Path.cwd", return_value=demo_path), patch(
            "qpip.cli.run_deactivate_command",
            return_value=0,
        ) as mocked:
            exit_code = main(["deactivate"])

        self.assertEqual(exit_code, 0)
        mocked.assert_called_once_with(("deactivate",), cwd=demo_path)


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