"""Tests for the mempalace skill.

Validates PalaceRoom, Mempalace class, factory constructors,
and the analyse_issues entry-point.

Refs: Epic #367, Sub-issue #368
"""

from __future__ import annotations

import json
import sys
import os
import time

import pytest

# Ensure the package is importable from the repo layout
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))

from mempalace.mempalace import Mempalace, PalaceRoom, analyse_issues


# ── PalaceRoom unit tests ─────────────────────────────────────────────────

class TestPalaceRoom:
    def test_store_and_retrieve(self):
        room = PalaceRoom(name="test", label="Test Room")
        room.store("key1", 42)
        assert room.retrieve("key1") == 42

    def test_retrieve_default(self):
        room = PalaceRoom(name="test", label="Test Room")
        assert room.retrieve("missing") is None
        assert room.retrieve("missing", "fallback") == "fallback"

    def test_summary_format(self):
        room = PalaceRoom(name="test", label="Test Room")
        room.store("repos", 5)
        summary = room.summary()
        assert "## Test Room" in summary
        assert "repos: 5" in summary

    def test_contents_default_factory_isolation(self):
        """Each room gets its own dict — no shared mutable default."""
        r1 = PalaceRoom(name="a", label="A")
        r2 = PalaceRoom(name="b", label="B")
        r1.store("x", 1)
        assert r2.retrieve("x") is None

    def test_entered_at_is_recent(self):
        before = time.time()
        room = PalaceRoom(name="t", label="T")
        after = time.time()
        assert before <= room.entered_at <= after


# ── Mempalace core tests ──────────────────────────────────────────────────

class TestMempalace:
    def test_add_and_enter_room(self):
        p = Mempalace(domain="test")
        p.add_room("r1", "Room 1")
        room = p.enter("r1")
        assert room.name == "r1"

    def test_enter_nonexistent_room_raises(self):
        p = Mempalace()
        with pytest.raises(KeyError, match="No room"):
            p.enter("ghost")

    def test_store_without_enter_raises(self):
        p = Mempalace()
        p.add_room("r", "R")
        with pytest.raises(RuntimeError, match="Enter a room"):
            p.store("k", "v")

    def test_store_and_retrieve_via_palace(self):
        p = Mempalace()
        p.add_room("r", "R")
        p.enter("r")
        p.store("count", 10)
        assert p.retrieve("r", "count") == 10

    def test_retrieve_missing_room_returns_default(self):
        p = Mempalace()
        assert p.retrieve("nope", "key") is None
        assert p.retrieve("nope", "key", 99) == 99

    def test_render_includes_domain(self):
        p = Mempalace(domain="audit")
        p.add_room("r", "Room")
        p.enter("r")
        p.store("item", "value")
        output = p.render()
        assert "audit" in output
        assert "Room" in output

    def test_to_dict_structure(self):
        p = Mempalace(domain="test")
        p.add_room("r", "R")
        p.enter("r")
        p.store("a", 1)
        d = p.to_dict()
        assert d["domain"] == "test"
        assert "elapsed_seconds" in d
        assert d["rooms"]["r"] == {"a": 1}

    def test_to_json_is_valid(self):
        p = Mempalace(domain="j")
        p.add_room("x", "X")
        p.enter("x")
        p.store("v", [1, 2, 3])
        parsed = json.loads(p.to_json())
        assert parsed["rooms"]["x"]["v"] == [1, 2, 3]


# ── Factory constructor tests ─────────────────────────────────────────────

class TestFactories:
    def test_for_issue_analysis_rooms(self):
        p = Mempalace.for_issue_analysis()
        assert p.domain == "issue_analysis"
        for key in ("repo_architecture", "assignment_status",
                    "triage_priority", "resolution_patterns"):
            p.enter(key)  # should not raise

    def test_for_health_check_rooms(self):
        p = Mempalace.for_health_check()
        assert p.domain == "health_check"
        for key in ("service_topology", "failure_signals", "recovery_history"):
            p.enter(key)

    def test_for_code_review_rooms(self):
        p = Mempalace.for_code_review()
        assert p.domain == "code_review"
        for key in ("change_scope", "risk_surface",
                    "test_coverage", "reviewer_context"):
            p.enter(key)


# ── analyse_issues entry-point tests ──────────────────────────────────────

class TestAnalyseIssues:
    SAMPLE_DATA = [
        {"repo": "the-nexus", "open_issues": 40, "assigned": 30, "unassigned": 10},
        {"repo": "timmy-home", "open_issues": 30, "assigned": 25, "unassigned": 5},
        {"repo": "hermes-agent", "open_issues": 20, "assigned": 15, "unassigned": 5},
        {"repo": "empty-repo", "open_issues": 0, "assigned": 0, "unassigned": 0},
    ]

    def test_returns_string(self):
        result = analyse_issues(self.SAMPLE_DATA)
        assert isinstance(result, str)
        assert len(result) > 0

    def test_contains_room_headers(self):
        result = analyse_issues(self.SAMPLE_DATA)
        assert "Repository Architecture" in result
        assert "Assignment Status" in result

    def test_coverage_below_target(self):
        result = analyse_issues(self.SAMPLE_DATA, target_assignee_rate=0.90)
        assert "BELOW TARGET" in result

    def test_coverage_meets_target(self):
        good_data = [
            {"repo": "a", "open_issues": 10, "assigned": 10, "unassigned": 0},
        ]
        result = analyse_issues(good_data, target_assignee_rate=0.80)
        assert "OK" in result

    def test_empty_repos_list(self):
        result = analyse_issues([])
        assert isinstance(result, str)

    def test_single_repo(self):
        data = [{"repo": "solo", "open_issues": 5, "assigned": 3, "unassigned": 2}]
        result = analyse_issues(data)
        assert "solo" in result or "issue_analysis" in result
