#!/usr/bin/env python3
"""list-sessions <name-or-id> — resolves what the owner SEES (a friendly name, or a
short/full session-id prefix, as printed by Claude Code's ListAgents peer-session
listing) to what he needs to RESUME (full session id + cwd + a copy-pasteable
`claude --resume` command). Exercised against constructed rows/transcripts so it
never touches the real machine's sessions or ~/.claude/projects."""

import contextlib
import io
import json
import os
import shutil
import sys
import tempfile
import unittest
from importlib.machinery import SourceFileLoader

SCRIPT = os.path.join(os.path.dirname(__file__), "..", "bin", "list-sessions")
ls = SourceFileLoader("list_sessions_resolve", SCRIPT).load_module()


def row(name="", title="", sid=None, cwd=None, session=None):
    return {
        "name": name,
        "title": title or name or "?",
        "sid": sid,
        "session": session or (sid[:8] if sid else "?"),
        "cwd": cwd,
    }


def run_resolution(query, rows, trans):
    buf = io.StringIO()
    with contextlib.redirect_stdout(buf):
        code = ls.print_resolution(query, rows, trans)
    return code, buf.getvalue()


class ResolveTest(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.mkdtemp(prefix="list-sessions-resolve-test-")

    def tearDown(self):
        shutil.rmtree(self.tmp, ignore_errors=True)

    def write_transcript(self, sid, cwd, project="proj"):
        d = os.path.join(self.tmp, project)
        os.makedirs(d, exist_ok=True)
        path = os.path.join(d, f"{sid}.jsonl")
        with open(path, "w") as f:
            f.write(json.dumps({"type": "user", "sessionId": sid, "cwd": cwd}) + "\n")
        return path

    def transcript_map_for(self, entries):
        """entries: list of (sid, cwd) -> the {sid.jsonl: (path, mtime)} shape
        transcript_map() itself returns, built from real files under self.tmp."""
        trans = {}
        for sid, cwd in entries:
            path = self.write_transcript(sid, cwd)
            trans[f"{sid}.jsonl"] = (path, os.path.getmtime(path))
        return trans

    # -- single match ---------------------------------------------------------

    def test_single_match_by_exact_name_prints_resume_command(self):
        rows = [row(name="memory-organizer-fluttering-owl", sid="13d7cfab-0000-4000-8000-000000000000",
                     cwd="/home/user/Projects/overdeck")]
        code, out = run_resolution("memory-organizer-fluttering-owl", rows, {})
        self.assertEqual(code, 0)
        self.assertEqual(
            out.strip(),
            "cd /home/user/Projects/overdeck && claude --resume 13d7cfab-0000-4000-8000-000000000000",
        )

    def test_single_match_by_short_id_prefix(self):
        rows = [row(name="FIRE", sid="9552f9aa-0000-4000-8000-000000000000", cwd="/home/user/Projects/x")]
        code, out = run_resolution("9552f9", rows, {})
        self.assertEqual(code, 0)
        self.assertIn("claude --resume 9552f9aa-0000-4000-8000-000000000000", out)

    # -- ambiguous --------------------------------------------------------------

    def test_ambiguous_match_exits_nonzero_and_lists_all(self):
        rows = [
            row(name="ci-cd-fix-a", sid="aaaaaaaa-0000-4000-8000-000000000000", cwd="/x/a"),
            row(name="ci-cd-fix-b", sid="bbbbbbbb-0000-4000-8000-000000000000", cwd="/x/b"),
        ]
        code, out = run_resolution("ci-cd-fix", rows, {})
        self.assertNotEqual(code, 0)
        self.assertIn("ci-cd-fix-a", out)
        self.assertIn("ci-cd-fix-b", out)
        # never silently picks one
        self.assertNotIn("cd /x/a && claude --resume", out)
        self.assertNotIn("cd /x/b && claude --resume", out)

    # -- no match -----------------------------------------------------------

    def test_no_match_exits_nonzero_and_says_so_plainly(self):
        rows = [row(name="unrelated-session", sid="cccccccc-0000-4000-8000-000000000000", cwd="/x/c")]
        code, out = run_resolution("totally-different-name", rows, {})
        self.assertNotEqual(code, 0)
        self.assertIn('No session matches "totally-different-name"', out)

    # -- unknown cwd degrades honestly, never fabricates -----------------------

    def test_unknown_cwd_never_fabricates_a_directory(self):
        rows = [row(name="orphan-session", sid="dddddddd-0000-4000-8000-000000000000", cwd=None)]
        code, out = run_resolution("orphan-session", rows, {})
        self.assertNotEqual(code, 0)
        self.assertNotIn("claude --resume", out)
        self.assertIn("directory unknown", out)

    # -- dead session resolved from its own transcript --------------------------

    def test_dead_session_resolves_from_transcript_when_not_live(self):
        trans = self.transcript_map_for([
            ("13d7cfab-1111-4000-8000-000000000000", "/home/user/Projects/press-zone"),
        ])
        code, out = run_resolution("13d7cf", [], trans)
        self.assertEqual(code, 0)
        self.assertEqual(
            out.strip(),
            "cd /home/user/Projects/press-zone && claude --resume 13d7cfab-1111-4000-8000-000000000000",
        )

    def test_dead_session_lookup_with_no_match_says_so(self):
        trans = self.transcript_map_for([("aaaaaaaa-1111-4000-8000-000000000000", "/x")])
        code, out = run_resolution("zzzzzz", [], trans)
        self.assertNotEqual(code, 0)
        self.assertIn("No session matches", out)


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