"""Tests for handoff data models and document templates."""

from __future__ import annotations

from claude_engram.calibration import Tier
from claude_engram.handoff import (
    ContextSample,
    HandoffState,
    SessionType,
    generate_handoff_document,
    should_generate_handoff,
)


class TestSessionTypeEnum:
    """SessionType enum has 6 values for different session kinds."""

    def test_coding_exists(self):
        assert SessionType.CODING is not None

    def test_debug_exists(self):
        assert SessionType.DEBUG is not None

    def test_gsd_execution_exists(self):
        assert SessionType.GSD_EXECUTION is not None

    def test_voyager_exists(self):
        assert SessionType.VOYAGER is not None

    def test_beacon_exists(self):
        assert SessionType.BEACON is not None

    def test_research_exists(self):
        assert SessionType.RESEARCH is not None

    def test_session_type_count(self):
        """Exactly 6 session types."""
        assert len(SessionType) == 6

    def test_string_values_for_json(self):
        """SessionType values are strings (JSON-serializable)."""
        for st in SessionType:
            assert isinstance(st.value, str)


class TestContextSample:
    """ContextSample dataclass holds a single observation point."""

    def test_has_used_pct(self):
        sample = ContextSample(used_pct=45.0, turn_number=5, timestamp="2026-02-02T12:00:00Z")
        assert sample.used_pct == 45.0

    def test_has_turn_number(self):
        sample = ContextSample(used_pct=45.0, turn_number=5, timestamp="2026-02-02T12:00:00Z")
        assert sample.turn_number == 5

    def test_has_timestamp(self):
        sample = ContextSample(used_pct=45.0, turn_number=5, timestamp="2026-02-02T12:00:00Z")
        assert sample.timestamp == "2026-02-02T12:00:00Z"

    def test_velocity_default_none(self):
        sample = ContextSample(used_pct=45.0, turn_number=5, timestamp="2026-02-02T12:00:00Z")
        assert sample.velocity is None

    def test_velocity_can_be_set(self):
        sample = ContextSample(
            used_pct=45.0, turn_number=5, timestamp="2026-02-02T12:00:00Z", velocity=2.5
        )
        assert sample.velocity == 2.5

    def test_mutable(self):
        """ContextSample is mutable (state changes during lifecycle)."""
        sample = ContextSample(used_pct=45.0, turn_number=5, timestamp="2026-02-02T12:00:00Z")
        sample.velocity = 3.0
        assert sample.velocity == 3.0


class TestHandoffState:
    """HandoffState dataclass tracks handoff readiness and content."""

    def test_default_ready_false(self):
        state = HandoffState(created_at="2026-02-02T12:00:00Z", session_type=SessionType.CODING)
        assert state.ready is False

    def test_default_tier_none(self):
        state = HandoffState(created_at="2026-02-02T12:00:00Z", session_type=SessionType.CODING)
        assert state.tier == Tier.NONE

    def test_default_document_none(self):
        state = HandoffState(created_at="2026-02-02T12:00:00Z", session_type=SessionType.CODING)
        assert state.document is None

    def test_default_consumed_false(self):
        state = HandoffState(created_at="2026-02-02T12:00:00Z", session_type=SessionType.CODING)
        assert state.consumed is False

    def test_default_snooze_until_pct_none(self):
        state = HandoffState(created_at="2026-02-02T12:00:00Z", session_type=SessionType.CODING)
        assert state.snooze_until_pct is None

    def test_default_snooze_count_zero(self):
        state = HandoffState(created_at="2026-02-02T12:00:00Z", session_type=SessionType.CODING)
        assert state.snooze_count == 0

    def test_has_session_type(self):
        state = HandoffState(created_at="2026-02-02T12:00:00Z", session_type=SessionType.DEBUG)
        assert state.session_type == SessionType.DEBUG

    def test_has_created_at(self):
        state = HandoffState(created_at="2026-02-02T12:00:00Z", session_type=SessionType.CODING)
        assert state.created_at == "2026-02-02T12:00:00Z"

    def test_mutable(self):
        """HandoffState is mutable (state transitions during lifecycle)."""
        state = HandoffState(created_at="2026-02-02T12:00:00Z", session_type=SessionType.CODING)
        state.ready = True
        state.tier = Tier.HANDOFF
        state.document = {"type": "coding"}
        assert state.ready is True
        assert state.tier == Tier.HANDOFF
        assert state.document == {"type": "coding"}


class TestGenerateHandoffDocumentCoding:
    """CODING template has keys: type, branch, files_modified, decisions, next_steps."""

    def test_coding_has_type(self):
        doc = generate_handoff_document(
            session_type=SessionType.CODING,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert doc["type"] == "coding"

    def test_coding_has_branch(self):
        doc = generate_handoff_document(
            session_type=SessionType.CODING,
            memories=[],
            tool_counts={},
            git_branch="feature/auth",
            used_pct=50.0,
            eta=10,
        )
        assert doc["branch"] == "feature/auth"

    def test_coding_has_files_modified(self):
        doc = generate_handoff_document(
            session_type=SessionType.CODING,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert "files_modified" in doc

    def test_coding_has_decisions(self):
        doc = generate_handoff_document(
            session_type=SessionType.CODING,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert "decisions" in doc

    def test_coding_has_next_steps(self):
        doc = generate_handoff_document(
            session_type=SessionType.CODING,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert "next_steps" in doc

    def test_coding_required_keys(self):
        doc = generate_handoff_document(
            session_type=SessionType.CODING,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert set(doc.keys()) >= {"type", "branch", "files_modified", "decisions", "next_steps"}


class TestGenerateHandoffDocumentDebug:
    """DEBUG template has keys: type, symptom, hypotheses, files_investigated, next_steps."""

    def test_debug_has_type(self):
        doc = generate_handoff_document(
            session_type=SessionType.DEBUG,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert doc["type"] == "debug"

    def test_debug_required_keys(self):
        doc = generate_handoff_document(
            session_type=SessionType.DEBUG,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert set(doc.keys()) >= {
            "type",
            "symptom",
            "hypotheses",
            "files_investigated",
            "next_steps",
        }


class TestGenerateHandoffDocumentGSD:
    """GSD_EXECUTION template has type, phase, plan_file, current_task, etc."""

    def test_gsd_has_type(self):
        doc = generate_handoff_document(
            session_type=SessionType.GSD_EXECUTION,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert doc["type"] == "gsd_execution"

    def test_gsd_required_keys(self):
        doc = generate_handoff_document(
            session_type=SessionType.GSD_EXECUTION,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert set(doc.keys()) >= {
            "type",
            "phase",
            "plan_file",
            "current_task",
            "completed_tasks",
            "wave",
            "next_steps",
        }


class TestGenerateHandoffDocumentVoyager:
    """VOYAGER template has keys: type, method, topic, findings, sources_consulted, next_steps."""

    def test_voyager_has_type(self):
        doc = generate_handoff_document(
            session_type=SessionType.VOYAGER,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert doc["type"] == "voyager"

    def test_voyager_required_keys(self):
        doc = generate_handoff_document(
            session_type=SessionType.VOYAGER,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert set(doc.keys()) >= {
            "type",
            "method",
            "topic",
            "findings",
            "sources_consulted",
            "next_steps",
        }


class TestGenerateHandoffDocumentBeacon:
    """BEACON template has type, query_context, reports_generated, etc."""

    def test_beacon_has_type(self):
        doc = generate_handoff_document(
            session_type=SessionType.BEACON,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert doc["type"] == "beacon"

    def test_beacon_required_keys(self):
        doc = generate_handoff_document(
            session_type=SessionType.BEACON,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert set(doc.keys()) >= {
            "type",
            "query_context",
            "reports_generated",
            "key_findings",
            "next_steps",
        }


class TestGenerateHandoffDocumentResearch:
    """RESEARCH template has type, topic, findings, questions_open, etc."""

    def test_research_has_type(self):
        doc = generate_handoff_document(
            session_type=SessionType.RESEARCH,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert doc["type"] == "research"

    def test_research_required_keys(self):
        doc = generate_handoff_document(
            session_type=SessionType.RESEARCH,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=50.0,
            eta=10,
        )
        assert set(doc.keys()) >= {
            "type",
            "topic",
            "findings",
            "questions_open",
            "sources_consulted",
            "next_steps",
        }


class TestGenerateHandoffDocumentEmptyInputs:
    """Empty inputs produce valid (minimal) documents, not errors (Gap G2)."""

    def test_empty_memories_coding(self):
        doc = generate_handoff_document(
            session_type=SessionType.CODING,
            memories=[],
            tool_counts={},
            git_branch="",
            used_pct=0.0,
            eta=0,
        )
        assert isinstance(doc, dict)
        assert doc["type"] == "coding"

    def test_empty_memories_debug(self):
        doc = generate_handoff_document(
            session_type=SessionType.DEBUG,
            memories=[],
            tool_counts={},
            git_branch="",
            used_pct=0.0,
            eta=0,
        )
        assert isinstance(doc, dict)
        assert doc["type"] == "debug"

    def test_empty_memories_gsd(self):
        doc = generate_handoff_document(
            session_type=SessionType.GSD_EXECUTION,
            memories=[],
            tool_counts={},
            git_branch="",
            used_pct=0.0,
            eta=0,
        )
        assert isinstance(doc, dict)
        assert doc["type"] == "gsd_execution"

    def test_empty_memories_voyager(self):
        doc = generate_handoff_document(
            session_type=SessionType.VOYAGER,
            memories=[],
            tool_counts={},
            git_branch="",
            used_pct=0.0,
            eta=0,
        )
        assert isinstance(doc, dict)
        assert doc["type"] == "voyager"

    def test_empty_memories_beacon(self):
        doc = generate_handoff_document(
            session_type=SessionType.BEACON,
            memories=[],
            tool_counts={},
            git_branch="",
            used_pct=0.0,
            eta=0,
        )
        assert isinstance(doc, dict)
        assert doc["type"] == "beacon"

    def test_empty_memories_research(self):
        doc = generate_handoff_document(
            session_type=SessionType.RESEARCH,
            memories=[],
            tool_counts={},
            git_branch="",
            used_pct=0.0,
            eta=0,
        )
        assert isinstance(doc, dict)
        assert doc["type"] == "research"


class TestGenerateHandoffDocumentMetadata:
    """Documents include usage metadata (used_pct, eta)."""

    def test_includes_used_pct(self):
        doc = generate_handoff_document(
            session_type=SessionType.CODING,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=72.5,
            eta=5,
        )
        assert doc["used_pct"] == 72.5

    def test_includes_eta(self):
        doc = generate_handoff_document(
            session_type=SessionType.CODING,
            memories=[],
            tool_counts={},
            git_branch="main",
            used_pct=72.5,
            eta=5,
        )
        assert doc["eta"] == 5


class TestGenerateHandoffDocumentWithMemories:
    """Documents extract info from memory dicts."""

    def test_coding_extracts_decisions_from_memories(self):
        memories = [
            {"content": "Decided to use dataclasses", "category": "decision"},
            {"content": "Fixed import error", "category": "lesson"},
        ]
        doc = generate_handoff_document(
            session_type=SessionType.CODING,
            memories=memories,
            tool_counts={"Write": 3, "Edit": 2},
            git_branch="feature/auth",
            used_pct=60.0,
            eta=8,
        )
        assert isinstance(doc["decisions"], list)
        assert len(doc["decisions"]) >= 1

    def test_debug_extracts_from_memories(self):
        memories = [
            {"content": "ImportError in module X", "category": "checkpoint"},
        ]
        doc = generate_handoff_document(
            session_type=SessionType.DEBUG,
            memories=memories,
            tool_counts={"Read": 5},
            git_branch="fix/bug",
            used_pct=55.0,
            eta=6,
        )
        assert isinstance(doc["hypotheses"], list)


class TestShouldGenerateHandoff:
    """should_generate_handoff() implements Gap G2 minimum-signal threshold."""

    def test_false_when_no_activity(self):
        """No tools used and no memories -> no handoff needed."""
        assert should_generate_handoff(tool_count_total=0, memory_count=0) is False

    def test_false_when_minimal_tools_no_memories(self):
        """< 5 tool calls and 0 memories -> no handoff."""
        assert should_generate_handoff(tool_count_total=4, memory_count=0) is False

    def test_true_when_enough_tools(self):
        """5+ tool calls -> generate handoff (regardless of memory count)."""
        assert should_generate_handoff(tool_count_total=5, memory_count=0) is True

    def test_true_when_has_memories(self):
        """Any stored memories -> generate handoff (regardless of tool count)."""
        assert should_generate_handoff(tool_count_total=0, memory_count=1) is True

    def test_true_when_both(self):
        """Both tools and memories -> generate handoff."""
        assert should_generate_handoff(tool_count_total=10, memory_count=5) is True

    def test_boundary_exactly_five_tools(self):
        """Exactly 5 tools with 0 memories -> True (boundary)."""
        assert should_generate_handoff(tool_count_total=5, memory_count=0) is True

    def test_boundary_four_tools_zero_memories(self):
        """4 tools with 0 memories -> False (just below threshold)."""
        assert should_generate_handoff(tool_count_total=4, memory_count=0) is False

    def test_boundary_four_tools_one_memory(self):
        """4 tools with 1 memory -> True (memory alone sufficient)."""
        assert should_generate_handoff(tool_count_total=4, memory_count=1) is True
