"""Tests for qpip.project_run."""

from __future__ import annotations

import io
import sys
import tempfile
import unittest
from pathlib import Path

from qpip.project_run import RunUsageError, build_scripts_section, run_project_command


class RunProjectCommandTests(unittest.TestCase):
    def write_pyproject(
        self,
        project_dir: Path,
        scripts: dict[str, str] | None = None,
        project_scripts: dict[str, str] | None = None,
        project_gui_scripts: dict[str, str] | None = None,
        poetry_scripts: dict[str, str] | None = None,
    ) -> None:
        lines = [
            "[build-system]",
            'requires = ["setuptools>=61.0", "wheel"]',
            'build-backend = "setuptools.build_meta"',
            "",
            "[project]",
            'name = "demo"',
            'version = "0.1.0"',
            "",
        ]

        if project_scripts:
            lines.append("[project.scripts]")
            for name, command in project_scripts.items():
                key = f'"{name}"' if ":" in name else name
                lines.append(f"{key} = \"{command}\"")
            lines.append("")

        if project_gui_scripts:
            lines.append("[project.gui-scripts]")
            for name, command in project_gui_scripts.items():
                key = f'"{name}"' if ":" in name else name
                lines.append(f"{key} = \"{command}\"")
            lines.append("")

        if poetry_scripts:
            lines.append("[tool.poetry.scripts]")
            for name, command in poetry_scripts.items():
                key = f'"{name}"' if ":" in name else name
                lines.append(f"{key} = \"{command}\"")
            lines.append("")

        if scripts:
            lines.extend([*build_scripts_section(scripts), ""])

        (project_dir / "pyproject.toml").write_text("\n".join(lines), encoding="utf-8")

    def test_run_without_name_lists_available_qpip_scripts(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            project_dir = Path(tmp_dir)
            self.write_pyproject(
                project_dir,
                scripts={
                    "test": "python -m unittest discover -s tests -v",
                    "lint:check": "python -m pip list",
                },
            )
            buffer = io.StringIO()

            exit_code = run_project_command(["run"], cwd=project_dir, output=buffer)

            self.assertEqual(exit_code, 0)
            self.assertIn("可用脚本:", buffer.getvalue())
            self.assertIn("test", buffer.getvalue())
            self.assertIn("lint:check", buffer.getvalue())

    def test_run_executes_named_qpip_script(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            project_dir = Path(tmp_dir)
            command = (
                f'"{sys.executable}" -c '
                '"from pathlib import Path; Path(\'ran.txt\').write_text(\'ok\', encoding=\'utf-8\')"'
            )
            self.write_pyproject(project_dir, scripts={"write": command})
            buffer = io.StringIO()

            exit_code = run_project_command(["run", "write"], cwd=project_dir, output=buffer)

            self.assertEqual(exit_code, 0)
            self.assertTrue((project_dir / "ran.txt").exists())
            self.assertEqual((project_dir / "ran.txt").read_text(encoding="utf-8"), "ok")
            self.assertIn("[qpip]", buffer.getvalue())

    def test_run_supports_dry_run_and_extra_args(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            project_dir = Path(tmp_dir)
            self.write_pyproject(project_dir, scripts={"serve": "python manage.py"})
            buffer = io.StringIO()

            exit_code = run_project_command(
                ["run", "serve", "--", "--port", "8000"],
                cwd=project_dir,
                output=buffer,
                dry_run=True,
            )

            self.assertEqual(exit_code, 0)
            self.assertIn("python manage.py", buffer.getvalue())
            self.assertIn("--port", buffer.getvalue())
            self.assertIn("8000", buffer.getvalue())

    def test_run_falls_back_to_project_scripts_entrypoints(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            project_dir = Path(tmp_dir)
            (project_dir / "demo_entry.py").write_text(
                "from pathlib import Path\nimport sys\n\ndef main():\n    Path('entry.txt').write_text('|'.join(sys.argv), encoding='utf-8')\n    return 0\n",
                encoding="utf-8",
            )
            self.write_pyproject(project_dir, project_scripts={"demo": "demo_entry:main"})
            buffer = io.StringIO()

            exit_code = run_project_command(
                ["run", "demo", "--", "alpha", "beta"],
                cwd=project_dir,
                output=buffer,
            )

            self.assertEqual(exit_code, 0)
            self.assertTrue((project_dir / "entry.txt").exists())
            self.assertEqual((project_dir / "entry.txt").read_text(encoding="utf-8"), "demo|alpha|beta")
            self.assertIn("demo_entry:main", buffer.getvalue())

    def test_run_lists_compatible_project_tables_when_qpip_scripts_absent(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            project_dir = Path(tmp_dir)
            self.write_pyproject(
                project_dir,
                project_scripts={"serve": "demo_entry:main"},
                project_gui_scripts={"desktop": "demo_gui:start"},
                poetry_scripts={"poetry-cmd": "demo_poetry:main"},
            )
            buffer = io.StringIO()

            exit_code = run_project_command(["run"], cwd=project_dir, output=buffer)

            self.assertEqual(exit_code, 0)
            self.assertIn("serve", buffer.getvalue())
            self.assertIn("project.scripts", buffer.getvalue())
            self.assertIn("desktop", buffer.getvalue())
            self.assertIn("project.gui-scripts", buffer.getvalue())
            self.assertIn("poetry-cmd", buffer.getvalue())
            self.assertIn("tool.poetry.scripts", buffer.getvalue())

    def test_run_prefers_qpip_scripts_over_fallback_tables(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            project_dir = Path(tmp_dir)
            self.write_pyproject(
                project_dir,
                scripts={"test": "python -m unittest discover -s tests -v"},
                project_scripts={"serve": "demo_entry:main"},
            )
            buffer = io.StringIO()

            exit_code = run_project_command(["run"], cwd=project_dir, output=buffer)

            self.assertEqual(exit_code, 0)
            self.assertIn("test", buffer.getvalue())
            self.assertNotIn("serve", buffer.getvalue())

    def test_run_raises_when_script_missing(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            project_dir = Path(tmp_dir)
            self.write_pyproject(project_dir, scripts={"test": "python -m unittest"})

            with self.assertRaises(RunUsageError):
                run_project_command(["run", "missing"], cwd=project_dir, output=io.StringIO())

    def test_run_raises_when_pyproject_missing(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            project_dir = Path(tmp_dir)

            with self.assertRaises(RunUsageError):
                run_project_command(["run", "test"], cwd=project_dir, output=io.StringIO())

    def test_run_raises_when_no_compatible_scripts_exist(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            project_dir = Path(tmp_dir)
            self.write_pyproject(project_dir)

            with self.assertRaises(RunUsageError):
                run_project_command(["run"], cwd=project_dir, output=io.StringIO())

    def test_run_rejects_extra_args_without_separator(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            project_dir = Path(tmp_dir)
            self.write_pyproject(project_dir, scripts={"test": "python -m unittest"})

            with self.assertRaises(RunUsageError):
                run_project_command(["run", "test", "-k", "api"], cwd=project_dir, output=io.StringIO())


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