"""tests/test_source_agent_field.py — R-156: source_agent field on SemanticClaim.

Tests:
 - Migration v24 applies cleanly
 - SemanticClaim dataclass has source_agent field defaulting to None
 - create_claim stores source_agent when provided
 - create_claim stores NULL when source_agent is omitted
 - browse_memories returns source_agent in items
 - get_memory_detail returns source_agent in claim dict
"""

from __future__ import annotations

import pytest

from cortex.storage.sqlite_adapter import SQLiteAdapter
from cortex.types.claims import SemanticClaim


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
async def adapter():
    """In-memory SQLiteAdapter with all migrations (including v24) applied."""
    a = SQLiteAdapter(db_path=":memory:")
    await a.initialize()
    yield a
    await a.close()


# ---------------------------------------------------------------------------
# Model tests
# ---------------------------------------------------------------------------

def test_semantic_claim_has_source_agent_field():
    """SemanticClaim dataclass must have source_agent field with default None."""
    assert "source_agent" in SemanticClaim.__dataclass_fields__
    default = SemanticClaim.__dataclass_fields__["source_agent"].default
    assert default is None


def test_semantic_claim_source_agent_default():
    """Instantiating SemanticClaim without source_agent gives None."""
    claim = SemanticClaim(
        id="test-id",
        owner_id="owner",
        subject="Andrew",
        predicate="uses",
        object_value="dark mode",
    )
    assert claim.source_agent is None


def test_semantic_claim_source_agent_set():
    """source_agent can be set at construction time."""
    claim = SemanticClaim(
        id="test-id",
        owner_id="owner",
        subject="Andrew",
        predicate="uses",
        object_value="dark mode",
        source_agent="eva",
    )
    assert claim.source_agent == "eva"


# ---------------------------------------------------------------------------
# Migration / storage tests
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_migration_v24_applies(adapter: SQLiteAdapter):
    """After initialize(), the semantic_claim table must have source_agent column."""
    conn = await adapter._get_conn()
    async with conn.execute("PRAGMA table_info(semantic_claim)") as cur:
        rows = await cur.fetchall()
    col_names = {row["name"] for row in rows}
    assert "source_agent" in col_names, (
        f"source_agent column missing from semantic_claim. Columns: {col_names}"
    )


@pytest.mark.asyncio
async def test_create_claim_stores_source_agent(adapter: SQLiteAdapter):
    """create_claim with source_agent='kira' stores it and returns it."""
    claim = await adapter.create_claim(
        subject="Andrew",
        predicate="works from",
        object_value="Bangkok",
        owner_id="test-owner",
        source_agent="kira",
    )
    assert claim.source_agent == "kira"

    # Verify round-trip from DB
    fetched = await adapter.get_claim(claim.id)
    assert fetched is not None
    assert fetched.source_agent == "kira"


@pytest.mark.asyncio
async def test_create_claim_without_source_agent_defaults_null(adapter: SQLiteAdapter):
    """create_claim without source_agent stores NULL."""
    claim = await adapter.create_claim(
        subject="Kira",
        predicate="runs on",
        object_value="Mac Mini",
        owner_id="test-owner",
    )
    assert claim.source_agent is None

    fetched = await adapter.get_claim(claim.id)
    assert fetched is not None
    assert fetched.source_agent is None


@pytest.mark.asyncio
async def test_browse_memories_returns_source_agent(adapter: SQLiteAdapter):
    """browse_memories items should include source_agent field."""
    await adapter.create_claim(
        subject="Sora",
        predicate="specializes in",
        object_value="research",
        owner_id="test-owner",
        source_agent="sora",
    )

    result = await adapter.browse_memories(owner_id="test-owner")
    assert result["total"] >= 1
    item = result["items"][0]
    assert "source_agent" in item, f"source_agent missing from browse item. Keys: {list(item.keys())}"
    assert item["source_agent"] == "sora"


@pytest.mark.asyncio
async def test_browse_memories_source_agent_null(adapter: SQLiteAdapter):
    """browse_memories items include source_agent=None when not set."""
    await adapter.create_claim(
        subject="Eva",
        predicate="lives on",
        object_value="lume VM",
        owner_id="test-owner",
    )

    result = await adapter.browse_memories(owner_id="test-owner")
    assert result["total"] >= 1
    item = result["items"][0]
    assert "source_agent" in item
    assert item["source_agent"] is None


@pytest.mark.asyncio
async def test_multiple_source_agents(adapter: SQLiteAdapter):
    """Claims from different agents can coexist; source_agent is per-claim."""
    await adapter.create_claim(
        subject="Andrew", predicate="notes", object_value="claim-a",
        owner_id="owner", source_agent="eva",
    )
    await adapter.create_claim(
        subject="Andrew", predicate="notes", object_value="claim-b",
        owner_id="owner", source_agent="kira",
    )
    await adapter.create_claim(
        subject="Andrew", predicate="notes", object_value="claim-c",
        owner_id="owner",  # no source_agent
    )

    result = await adapter.browse_memories(owner_id="owner")
    assert result["total"] == 3

    agents = {item["source_agent"] for item in result["items"]}
    assert agents == {"eva", "kira", None}
