"""Independent checks for the desktop teaching sample, using disposable data."""

import copy
import json
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
from unittest.mock import patch


HERE = Path(__file__).resolve()
PROJECT = HERE.parent if (HERE.parent / "todo_core.py").is_file() else HERE.parents[3] / "todo-ai-workflow" / "lesson14_desktop"
sys.path.insert(0, str(PROJECT))
from todo_core import TodoCore


class DesktopContract(unittest.TestCase):
    def setUp(self):
        self.directory = tempfile.TemporaryDirectory()
        self.addCleanup(self.directory.cleanup)
        self.path = Path(self.directory.name) / "独立课堂数据.json"
        self.core = TodoCore(str(self.path))
        self.first = self.core.add_task("完成Python练习")
        self.core.add_task("整理课堂笔记")

    def assert_failed_save_rolls_back(self, operation):
        before = copy.deepcopy(self.core.get_tasks())
        original = self.path.read_bytes()
        with patch("todo_core.os.replace", side_effect=PermissionError("课堂模拟写入失败")):
            with self.assertRaises(OSError):
                operation()
        self.assertEqual(self.core.get_tasks(), before)
        self.assertEqual(self.path.read_bytes(), original)

    def test_add_failure_does_not_claim_saved_or_mutate_tasks(self):
        self.assert_failed_save_rolls_back(lambda: self.core.add_task("不应保存成功"))

    def test_complete_failure_preserves_pending_state(self):
        self.assert_failed_save_rolls_back(lambda: self.core.complete_task(self.first["id"]))

    def test_delete_failure_preserves_existing_task(self):
        self.assert_failed_save_rolls_back(lambda: self.core.delete_task(self.first["id"]))

    def test_invalid_task_fields_are_rejected_without_changing_file(self):
        valid = dict(id="task-1", title="完成Python练习", completed=False)
        invalid = [
            {},
            dict(valid, title="   "),
            dict(valid, completed="false"),
            dict(valid, title=123),
            dict(valid, id=None),
        ]
        for record in invalid:
            with self.subTest(record=record):
                self.path.write_text(json.dumps(dict(tasks=[record]), ensure_ascii=False), encoding="utf-8")
                original = self.path.read_bytes()
                with self.assertRaises(ValueError):
                    TodoCore(str(self.path))
                self.assertEqual(self.path.read_bytes(), original)

    def test_completed_state_survives_a_new_python_process(self):
        self.core.complete_task(self.first["id"])
        code = (
            "import json, sys; from todo_core import TodoCore; "
            "c=TodoCore(sys.argv[1]); "
            "print(json.dumps(dict(tasks=c.get_tasks(),stats=c.get_stats()),ensure_ascii=False))"
        )
        result = subprocess.run(
            [sys.executable, "-B", "-c", code, str(self.path)],
            cwd=PROJECT, text=True, capture_output=True, check=True,
        )
        payload = json.loads(result.stdout)
        self.assertEqual(payload["tasks"], self.core.get_tasks())
        self.assertEqual(payload["stats"], dict(total=2, completed=1, rate=0.5))


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