import subprocess
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch, MagicMock
from bot.lib import verify


class TestVerify(unittest.TestCase):
    def setUp(self):
        self.tmp = Path(tempfile.mkdtemp())
        self.repo = self.tmp / "repo"
        self.repo.mkdir()
        self.worktree = self.tmp / "wt"
        self.worktree.mkdir()
        # Provide a CritMatic.lua in the worktree so grep check has something to scan.
        (self.worktree / "CritMatic.lua").write_text("local x = 1\n", encoding="utf-8")

    def _make_fakes(self, *, changed_files="CritMatic.lua\n", luac_ok=True, packager_ok=True,
                    shortstat="1 file changed, 5 insertions(+), 2 deletions(-)\n"):
        def run(cmd, *args, **kwargs):
            result = MagicMock(returncode=0, stdout="", stderr="")
            if cmd[0] == "git" and "diff" in cmd and "--name-only" in cmd:
                result.stdout = changed_files
            elif cmd[0] == "git" and "diff" in cmd and "--shortstat" in cmd:
                result.stdout = shortstat
            elif cmd[0] == "luac":
                result.returncode = 0 if luac_ok else 1
                result.stderr = "" if luac_ok else "lua: syntax error"
            elif cmd[0] == "bash" and len(cmd) > 1 and "release.sh" in cmd[1]:
                result.returncode = 0 if packager_ok else 1
            return result
        return run

    def test_all_checks_pass(self):
        # Make release.sh exist so packager check actually runs.
        (self.repo / ".release").mkdir()
        (self.repo / ".release" / "release.sh").write_text("#!/bin/bash\nexit 0\n", encoding="utf-8")
        with patch("subprocess.run", side_effect=self._make_fakes()):
            r = verify.verify(
                worktree_path=self.worktree,
                removed_symbols={"GetDeadAPI"},
                repo_root=self.repo,
            )
        self.assertTrue(r.passed_all, [(row.name, row.detail) for row in r.rows])
        self.assertEqual(len(r.rows), 5)

    def test_luac_failure_fails_overall(self):
        (self.repo / ".release").mkdir()
        (self.repo / ".release" / "release.sh").write_text("#!/bin/bash\nexit 0\n", encoding="utf-8")
        with patch("subprocess.run", side_effect=self._make_fakes(luac_ok=False)):
            r = verify.verify(
                worktree_path=self.worktree,
                removed_symbols=set(),
                repo_root=self.repo,
            )
        self.assertFalse(r.passed_all)
        luac_row = next(row for row in r.rows if row.name == "luac -p")
        self.assertFalse(luac_row.passed)

    def test_packager_missing_is_pass_with_note(self):
        # No .release/release.sh exists.
        with patch("subprocess.run", side_effect=self._make_fakes()):
            r = verify.verify(
                worktree_path=self.worktree,
                removed_symbols=set(),
                repo_root=self.repo,
            )
        pkg = next(row for row in r.rows if row.name == "packager dry-run")
        self.assertTrue(pkg.passed)
        self.assertIn("not present", pkg.detail.lower())

    def test_packager_worktree_incompat_is_pass_with_skip_note(self):
        # BigWigs packager bails out inside a git worktree because it can't
        # find a `.git` directory. Treat as a skipped pass.
        (self.repo / ".release").mkdir()
        (self.repo / ".release" / "release.sh").write_text("#!/bin/bash\nexit 0\n", encoding="utf-8")

        def fakes(cmd, *args, **kwargs):
            result = MagicMock(returncode=0, stdout="", stderr="")
            if cmd[0] == "git" and "diff" in cmd and "--name-only" in cmd:
                result.stdout = "CritMatic.lua\n"
            elif cmd[0] == "git" and "diff" in cmd and "--shortstat" in cmd:
                result.stdout = "1 file changed, 1 insertions(+), 0 deletions(-)\n"
            elif cmd[0] == "luac":
                result.returncode = 0
            elif cmd[0] == "bash" and len(cmd) > 1 and "release.sh" in cmd[1]:
                result.returncode = 1
                result.stderr = 'No Git, SVN, or Hg checkout found in "/tmp/wt".\n'
            return result

        with patch("subprocess.run", side_effect=fakes):
            r = verify.verify(
                worktree_path=self.worktree,
                removed_symbols=set(),
                repo_root=self.repo,
            )
        pkg = next(row for row in r.rows if row.name == "packager dry-run")
        self.assertTrue(pkg.passed)
        self.assertIn("worktree", pkg.detail.lower())

    def test_disallowed_path_fails(self):
        with patch("subprocess.run", side_effect=self._make_fakes(
            changed_files="Libs/Ace3.lua\n",
        )):
            r = verify.verify(
                worktree_path=self.worktree,
                removed_symbols=set(),
                repo_root=self.repo,
            )
        self.assertFalse(r.passed_all)
        path_row = next(row for row in r.rows if row.name == "path allowlist")
        self.assertFalse(path_row.passed)
        self.assertIn("Libs/", path_row.detail)

    def test_diff_size_cap_fails(self):
        with patch("subprocess.run", side_effect=self._make_fakes(
            shortstat="3 files changed, 250 insertions(+), 0 deletions(-)\n",
        )):
            r = verify.verify(
                worktree_path=self.worktree,
                removed_symbols=set(),
                repo_root=self.repo,
            )
        size_row = next(row for row in r.rows if row.name == "diff size")
        self.assertFalse(size_row.passed)

    def test_removed_symbol_still_present_fails(self):
        # Put a removed symbol back into the worktree's source.
        (self.worktree / "CritMatic.lua").write_text("local x = GetDeadAPI()\n", encoding="utf-8")
        with patch("subprocess.run", side_effect=self._make_fakes()):
            r = verify.verify(
                worktree_path=self.worktree,
                removed_symbols={"GetDeadAPI"},
                repo_root=self.repo,
            )
        grep_row = next(row for row in r.rows if row.name == "removed-symbol grep")
        self.assertFalse(grep_row.passed)


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