import unittest
from pathlib import Path

from tests.support.harness import TmuxOnlyTestCase


class RecoveryTests(TmuxOnlyTestCase):
    def _manifest_path_for(self, sid):
        return Path(
            self.server._recovery_manifest_path_for_session(self.server.sessions[sid])
        )

    def _manifest_json_for(self, sid):
        path = self._manifest_path_for(sid)
        with path.open("r", encoding="utf-8") as f:
            return self.server.json.load(f)

    def test_recover_dedicated_tmux_named_session(self):
        sid = self.server.pty_spawn(
            "read line; printf 'ded:%s' \"$line\"",
            owner="recover-owner",
            scope="read-write",
            backend="tmux",
            name="recover-ded",
        )
        self.server.pty_attach(sid, "client-a")
        self.server.pty_handoff(
            sid,
            client_id="client-a",
            target_client_id="client-b",
            reason="operator handoff",
        )

        self.server.sessions.pop(sid)
        recovered = self.server.pty_recover("recover-ded", owner="recover-owner")

        self.assertTrue(recovered["recovered"])
        self.assertFalse(recovered["attached"])
        self.assertEqual(recovered["last_lease_event"], "handoff")
        self.assertEqual(recovered["last_lease_to_client_id"], "client-b")
        self.assertFalse(recovered["already_live"])

    def test_recover_workspace_as_all_or_nothing_group(self):
        sid_a = self.server.pty_spawn(
            "read line; printf 'a:%s' \"$line\"",
            owner="recover-ws-owner",
            scope="read-write",
            backend="tmux",
            tmux_workspace="recover-ws",
            name="recover-a",
        )
        sid_b = self.server.pty_spawn(
            "read line; printf 'b:%s' \"$line\"",
            owner="recover-ws-owner",
            scope="read-write",
            backend="tmux",
            tmux_workspace="recover-ws",
            name="recover-b",
        )

        self.server.pty_attach(sid_a, "client-a")
        self.server.pty_attach(sid_b, "client-a")
        self.server.pty_handoff(
            sid_a,
            client_id="client-a",
            target_client_id="client-b",
            reason="workspace handoff",
        )

        self.server.sessions.pop(sid_a)
        self.server.sessions.pop(sid_b)

        recovered = self.server.pty_recover("recover-a", owner="recover-ws-owner")
        self.assertTrue(recovered["recovered"])
        self.assertEqual(recovered["recovery_group_session_count"], 2)
        self.assertFalse(recovered["attached"])

        resumed_other = self.server.pty_resume("recover-b", owner="recover-ws-owner")
        self.assertTrue(resumed_other["recovered"])
        self.assertFalse(resumed_other["attached"])
        self.assertEqual(resumed_other["last_lease_event"], "handoff")

    def test_recover_refuses_when_pane_missing(self):
        sid_a = self.server.pty_spawn(
            "sleep 30",
            owner="missing-owner",
            scope="read-write",
            backend="tmux",
            tmux_workspace="missing-ws",
            name="missing-a",
        )
        sid_b = self.server.pty_spawn(
            "sleep 30",
            owner="missing-owner",
            scope="read-write",
            backend="tmux",
            tmux_workspace="missing-ws",
            name="missing-b",
        )

        tmux_pane = self.server.sessions[sid_b]["tmux_pane"]
        self.server.sessions.pop(sid_a)
        self.server.sessions.pop(sid_b)
        self.server._tmux_run(["kill-pane", "-t", tmux_pane])

        with self.assertRaises(RuntimeError) as ctx:
            self.server.pty_recover("missing-a", owner="missing-owner")

        self.assertIn("recovery pane is missing", str(ctx.exception))

    def test_recover_returns_already_live_for_existing_session(self):
        sid = self.server.pty_spawn(
            "sleep 30",
            owner="already-live-owner",
            scope="read-write",
            backend="tmux",
            name="already-live",
        )

        recovered = self.server.pty_recover("already-live", owner="already-live-owner")

        self.assertTrue(recovered["already_live"])
        self.assertEqual(recovered["recovered_via"], "recover")
        self.assertEqual(recovered["session_id"], sid)
        self.assertFalse(recovered["recovered"])

    def test_manifest_updates_after_resize_and_lease_changes(self):
        sid = self.server.pty_spawn(
            "sleep 30",
            owner="manifest-owner",
            scope="read-write",
            backend="tmux",
            name="manifest-basic",
        )

        initial = self._manifest_json_for(sid)
        member = initial["members"][0]
        self.assertEqual(initial["schema_version"], 1)
        self.assertEqual(member["cols"], 120)
        self.assertEqual(member["rows"], 30)
        self.assertIsNone(member["last_lease_event"])

        self.server.pty_resize(sid, 150, 42)
        resized = self._manifest_json_for(sid)
        resized_member = resized["members"][0]
        self.assertEqual(resized_member["cols"], 150)
        self.assertEqual(resized_member["rows"], 42)
        self.assertGreaterEqual(resized["updated_at"], initial["updated_at"])

        self.server.pty_attach(sid, "client-a")
        attached = self._manifest_json_for(sid)["members"][0]
        self.assertEqual(attached["last_lease_event"], "attach")
        self.assertEqual(attached["last_lease_to_client_id"], "client-a")

        self.server.pty_handoff(
            sid,
            client_id="client-a",
            target_client_id="client-b",
            reason="manifest handoff",
        )
        handed = self._manifest_json_for(sid)["members"][0]
        self.assertEqual(handed["last_lease_event"], "handoff")
        self.assertEqual(handed["last_lease_from_client_id"], "client-a")
        self.assertEqual(handed["last_lease_to_client_id"], "client-b")
        self.assertEqual(handed["last_lease_reason"], "manifest handoff")

        self.server.pty_detach(sid, "client-b")
        detached = self._manifest_json_for(sid)["members"][0]
        self.assertEqual(detached["last_lease_event"], "detach")
        self.assertEqual(detached["last_lease_from_client_id"], "client-b")
        self.assertIsNone(detached["last_lease_to_client_id"])

    def test_manifest_removed_after_close(self):
        sid = self.server.pty_spawn(
            "sleep 30",
            owner="cleanup-owner",
            scope="read-write",
            backend="tmux",
            name="cleanup-close",
        )
        path = self._manifest_path_for(sid)
        self.assertTrue(path.exists())

        self.server.pty_close(sid)

        self.assertFalse(path.exists())

    def test_manifest_removed_after_gc_timeout_cleanup(self):
        sid = self.server.pty_spawn(
            "sleep 30",
            owner="cleanup-owner",
            scope="read-write",
            backend="tmux",
            name="cleanup-gc",
        )
        path = self._manifest_path_for(sid)
        self.assertTrue(path.exists())

        self.server.sessions[sid]["last_active"] = 0
        self.server.gc_sessions(force=True)

        self.assertNotIn(sid, self.server.sessions)
        self.assertFalse(path.exists())


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