import json
import subprocess
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch, MagicMock
from bot import approve
from bot.lib import state


class TestApproveList(unittest.TestCase):
    def setUp(self):
        self.tmp = Path(tempfile.mkdtemp())
        self.state_dir = self.tmp / "state"
        self.state_dir.mkdir()
        self.repo_root = self.tmp / "repo"
        self.repo_root.mkdir()

    def test_list_empty(self):
        out = approve.cmd_list(self.state_dir)
        self.assertIn("no pending fixes", out.lower())

    def test_list_renders_entries(self):
        state.record_pending_fix(self.state_dir, "bot/a", {
            "status": "auto-ship-eligible", "confidence": 88,
            "report_path": "bot/reports/a.md", "worktree_path": "/tmp/a",
            "created_at": "t", "verify_rows": [], "rationale": "ok",
        })
        state.record_pending_fix(self.state_dir, "bot/b", {
            "status": "held-for-review", "confidence": 50,
            "report_path": "bot/reports/b.md", "worktree_path": "/tmp/b",
            "created_at": "t", "verify_rows": [], "rationale": "no",
        })
        out = approve.cmd_list(self.state_dir)
        self.assertIn("bot/a", out)
        self.assertIn("bot/b", out)
        self.assertIn("auto-ship-eligible", out)
        self.assertIn("held-for-review", out)


class TestApproveMerge(unittest.TestCase):
    def setUp(self):
        self.tmp = Path(tempfile.mkdtemp())
        self.state_dir = self.tmp / "state"
        self.state_dir.mkdir()
        self.repo_root = self.tmp / "repo"
        self.repo_root.mkdir()
        self.worktree = self.tmp / "wt"
        self.worktree.mkdir()
        state.record_pending_fix(self.state_dir, "bot/x", {
            "status": "auto-ship-eligible", "confidence": 90,
            "report_path": "bot/reports/x.md",
            "worktree_path": str(self.worktree),
            "created_at": "t", "verify_rows": [], "rationale": "ok",
        })

    def test_merge_unknown_branch_errors(self):
        rc, msg = approve.cmd_merge(self.state_dir, self.repo_root, "bot/nonexistent")
        self.assertNotEqual(rc, 0)
        self.assertIn("not found", msg.lower())

    def test_merge_runs_verify_then_ff_merge(self):
        from bot.lib.verify import VerifyResult, VerifyRow
        passing = VerifyResult(passed_all=True, rows=[VerifyRow("luac -p", True, "ok")])
        with patch("bot.approve.run_verify", return_value=passing), \
             patch("subprocess.run") as sp:
            sp.return_value = MagicMock(returncode=0, stdout="", stderr="")
            rc, msg = approve.cmd_merge(self.state_dir, self.repo_root, "bot/x")
        self.assertEqual(rc, 0)
        merge_call = next(c for c in sp.call_args_list if "merge" in c.args[0])
        self.assertIn("--ff-only", merge_call.args[0])
        # pending entry removed
        self.assertNotIn("bot/x", state.load_pending_fixes(self.state_dir))

    def test_merge_aborts_when_verify_fails(self):
        from bot.lib.verify import VerifyResult, VerifyRow
        failing = VerifyResult(passed_all=False, rows=[VerifyRow("luac -p", False, "syntax err")])
        with patch("bot.approve.run_verify", return_value=failing), \
             patch("subprocess.run") as sp:
            rc, msg = approve.cmd_merge(self.state_dir, self.repo_root, "bot/x")
        self.assertNotEqual(rc, 0)
        self.assertIn("verify", msg.lower())
        merge_calls = [c for c in sp.call_args_list if "merge" in c.args[0]]
        self.assertEqual(merge_calls, [])
        # entry still present (not consumed)
        self.assertIn("bot/x", state.load_pending_fixes(self.state_dir))


class TestApproveReject(unittest.TestCase):
    def setUp(self):
        self.tmp = Path(tempfile.mkdtemp())
        self.state_dir = self.tmp / "state"
        self.state_dir.mkdir()
        self.repo_root = self.tmp / "repo"
        self.repo_root.mkdir()
        self.worktree = self.tmp / "wt"
        self.worktree.mkdir()
        state.record_pending_fix(self.state_dir, "bot/y", {
            "status": "held-for-review", "confidence": 30,
            "report_path": "bot/reports/y.md",
            "worktree_path": str(self.worktree),
            "created_at": "t", "verify_rows": [], "rationale": "no",
        })

    def test_reject_removes_worktree_and_entry(self):
        with patch("subprocess.run") as sp:
            sp.return_value = MagicMock(returncode=0, stdout="", stderr="")
            rc, msg = approve.cmd_reject(self.state_dir, self.repo_root, "bot/y")
        self.assertEqual(rc, 0)
        worktree_calls = [c for c in sp.call_args_list if "worktree" in c.args[0]]
        self.assertTrue(any("remove" in c.args[0] for c in worktree_calls))
        self.assertNotIn("bot/y", state.load_pending_fixes(self.state_dir))


class TestApproveCancel(unittest.TestCase):
    def setUp(self):
        self.tmp = Path(tempfile.mkdtemp())
        self.state_dir = self.tmp / "state"
        self.state_dir.mkdir()
        self.repo_root = self.tmp / "repo"
        self.repo_root.mkdir()
        state.record_pending_release(self.state_dir, "v5.5.8", {
            "branch": "wow", "tag": "v5.5.8",
            "scheduled_at": "2026-05-12T09:14:23.456-07:00",
            "status": "scheduled",
            "fix_branch": "bot/patch-wow-12_0_5",
            "report_path": "bot/reports/x.md",
            "created_at": "2026-05-07T11:24:18Z",
            "retries": 0,
        })

    def test_cancel_removes_entry(self):
        rc, msg = approve.cmd_cancel(self.state_dir, "v5.5.8")
        self.assertEqual(rc, 0)
        self.assertNotIn("v5.5.8", state.load_pending_releases(self.state_dir))

    def test_cancel_unknown_version_errors(self):
        rc, msg = approve.cmd_cancel(self.state_dir, "v9.9.9")
        self.assertNotEqual(rc, 0)
        self.assertIn("not found", msg.lower())


class TestApproveListIncludesReleases(unittest.TestCase):
    def setUp(self):
        self.tmp = Path(tempfile.mkdtemp())
        self.state_dir = self.tmp / "state"
        self.state_dir.mkdir()

    def test_list_shows_scheduled_releases(self):
        state.record_pending_release(self.state_dir, "v5.5.8", {
            "branch": "wow", "tag": "v5.5.8",
            "scheduled_at": "2026-05-12T09:14:23.456-07:00",
            "status": "scheduled", "retries": 0,
        })
        out = approve.cmd_list(self.state_dir)
        self.assertIn("v5.5.8", out)
        self.assertIn("scheduled", out)


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