"""
Tests for cortex/hippo/tools/describe.py — hippo_describe tool.

Issue: #1118
"""

from __future__ import annotations

import pytest
import pytest_asyncio

from cortex.hippo.config import HippoConfig
from cortex.hippo.engine import HippoEngine
from cortex.hippo.retrieval import RetrievalEngine
from cortex.hippo.tools.describe import execute_describe


# ═══════════════════════════════════════════════════════════════════════════
# Fixtures
# ═══════════════════════════════════════════════════════════════════════════


@pytest.fixture
def hippo_config() -> HippoConfig:
    return HippoConfig(enabled=True, db_path=":memory:", fresh_tail_count=4, max_tokens=10_000)


@pytest_asyncio.fixture
async def engine(hippo_config):
    eng = HippoEngine(config=hippo_config)
    await eng.initialize(db_path=":memory:", owner_id="test-owner", conversation_id="test-session")
    yield eng
    await eng.shutdown()


@pytest_asyncio.fixture
async def retrieval(engine):
    return RetrievalEngine(
        conversation_store=engine.conversation_store,
        summary_store=engine.summary_store,
    )


@pytest_asyncio.fixture
async def seeded(engine, retrieval):
    """Engine + retrieval with summaries pre-loaded."""
    summary_store = engine.summary_store
    conv_id = engine._conversation.conversation_id

    s1 = await summary_store.create_summary(
        conversation_id=conv_id,
        owner_id="test-owner",
        kind="leaf",
        depth=0,
        content="Deployed app to production.",
        token_count=10,
        earliest_at="2026-03-01T00:00:00Z",
        latest_at="2026-03-01T01:00:00Z",
        descendant_count=0,
        descendant_token_count=0,
        source_message_token_count=20,
    )

    s2 = await summary_store.create_summary(
        conversation_id=conv_id,
        owner_id="test-owner",
        kind="condensed",
        depth=1,
        content="Sprint summary: deployed app, fixed bugs.",
        token_count=15,
        earliest_at="2026-03-01T00:00:00Z",
        latest_at="2026-03-01T02:00:00Z",
        descendant_count=1,
        descendant_token_count=10,
        source_message_token_count=30,
    )

    # Link s1 as child of s2 (child's ID, parent's ID)
    await summary_store.add_summary_parent(s1.summary_id, s2.summary_id)

    return {
        "engine": engine,
        "retrieval": retrieval,
        "s1": s1,
        "s2": s2,
        "conv_id": conv_id,
    }


# ═══════════════════════════════════════════════════════════════════════════
# Tests
# ═══════════════════════════════════════════════════════════════════════════


@pytest.mark.asyncio
async def test_summary_lookup_renders_metadata(seeded):
    """Summary describe returns formatted metadata block with manifest."""
    r = seeded["retrieval"]
    s2 = seeded["s2"]

    result = await execute_describe(
        params={"id": s2.summary_id, "allConversations": True},
        retrieval=r,
        owner_id="test-owner",
    )

    text = result["content"][0]["text"]
    assert f"LCM_SUMMARY {s2.summary_id}" in text
    assert "kind=condensed" in text
    assert "depth=1" in text
    assert "manifest" in text
    assert "content" in text
    assert "Sprint summary" in text


@pytest.mark.asyncio
async def test_summary_lookup_includes_parent_child_ids(seeded):
    """Describe shows parent and child lineage."""
    r = seeded["retrieval"]
    s1 = seeded["s1"]
    s2 = seeded["s2"]

    result = await execute_describe(
        params={"id": s2.summary_id, "allConversations": True},
        retrieval=r,
        owner_id="test-owner",
    )

    text = result["content"][0]["text"]
    assert f"children {s1.summary_id}" in text

    # s1 should show s2 as parent
    result_s1 = await execute_describe(
        params={"id": s1.summary_id, "allConversations": True},
        retrieval=r,
        owner_id="test-owner",
    )
    text_s1 = result_s1["content"][0]["text"]
    assert f"parents {s2.summary_id}" in text_s1


@pytest.mark.asyncio
async def test_file_lookup(engine, retrieval):
    """File describe returns formatted file metadata."""
    summary_store = engine.summary_store
    conv_id = engine._conversation.conversation_id

    # Insert a large file record
    await summary_store._conn.execute(
        """
        INSERT INTO hippo_large_files
            (file_id, conversation_id, owner_id, file_name, mime_type,
             byte_size, content, exploration_summary, created_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        """,
        (
            "file_abc123",
            conv_id,
            "test-owner",
            "report.pdf",
            "application/pdf",
            12345,
            "",
            "This is a financial report for Q1 2026.",
            "2026-03-01T00:00:00Z",
        ),
    )
    await summary_store._conn.commit()

    result = await execute_describe(
        params={"id": "file_abc123", "allConversations": True},
        retrieval=retrieval,
        owner_id="test-owner",
    )

    text = result["content"][0]["text"]
    assert "## LCM File: file_abc123" in text
    assert "report.pdf" in text
    assert "application/pdf" in text
    assert "12,345 bytes" in text
    assert "financial report" in text


@pytest.mark.asyncio
async def test_missing_id_error(retrieval):
    """Describe returns error for non-existent ID."""
    result = await execute_describe(
        params={"id": "sum_nonexistent", "allConversations": True},
        retrieval=retrieval,
        owner_id="test-owner",
    )

    text = result["content"][0]["text"]
    assert "Not found" in text
    assert "sum_nonexistent" in text


@pytest.mark.asyncio
async def test_no_conversation_scope_error(retrieval):
    """Describe returns error when no conversation scope can be resolved."""
    result = await execute_describe(
        params={"id": "sum_xyz"},
        retrieval=retrieval,
        owner_id="test-owner",
    )

    text = result["content"][0]["text"]
    assert "No LCM conversation found" in text


@pytest.mark.asyncio
async def test_token_cap_annotation(seeded):
    """Budget-fit annotations reflect the requested tokenCap."""
    r = seeded["retrieval"]
    s2 = seeded["s2"]

    # With a tiny token cap, everything should be "over"
    result = await execute_describe(
        params={"id": s2.summary_id, "allConversations": True, "tokenCap": 1},
        retrieval=r,
        owner_id="test-owner",
    )

    details = result["details"]
    assert details["manifest"]["tokenCap"] == 1
    assert details["manifest"]["budgetSource"] == "request"

    # Check the text also has budget info
    text = result["content"][0]["text"]
    assert "budgetCap=1" in text

    # With a large cap, should be "in"
    result2 = await execute_describe(
        params={"id": s2.summary_id, "allConversations": True, "tokenCap": 100_000},
        retrieval=r,
        owner_id="test-owner",
    )

    text2 = result2["content"][0]["text"]
    assert "budget[s=in" in text2


@pytest.mark.asyncio
async def test_conversation_scope_mismatch(seeded):
    """Describe returns error when item is in different conversation."""
    r = seeded["retrieval"]
    s1 = seeded["s1"]

    # Use a wrong conversation ID
    result = await execute_describe(
        params={"id": s1.summary_id, "conversationId": 99999},
        retrieval=r,
        owner_id="test-owner",
    )

    text = result["content"][0]["text"]
    assert "Not found in conversation 99999" in text
