"""
tests/test_wake_pipeline_e2e.py — Wake pipeline W1-W4 end-to-end integration tests.

Covers the full wake path:
  W1: boot with commitments (due_commitments surfaced in WakeReport)
  W2: boot with open loops (open_loops surfaced in WakeReport)
  W3: boot with cornerstones (cornerstones_loaded in WakeReport)
  W4: empty boot (no commitments, no loops, no cornerstones — clean slate)

Each test uses a real SQLiteAdapter (file-backed, isolated via tmp_path) and
a TypedMockLLMProvider, matching the integration conftest.py adapter pattern.
"""
from __future__ import annotations

import pytest
import pytest_asyncio

from tests.integration.conftest import _build_plugin
from tests.integration.mock_llm import TypedMockLLMProvider


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


@pytest.fixture
def mock_llm() -> TypedMockLLMProvider:
    """Typed mock LLM with default extraction claim."""
    return TypedMockLLMProvider()


@pytest_asyncio.fixture
async def plugin(tmp_path, mock_llm):
    """
    CortexPlugin wired with a real SQLite file DB (isolated per test)
    and the typed mock LLM. Storage is closed after each test.
    """
    db_path = str(tmp_path / "wake_e2e_test.db")
    p, storage = await _build_plugin(db_path, mock_llm)
    yield p
    await storage.close()


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


async def _add_commitment(storage, owner_id: str, content: str, due_at: str) -> None:
    """Insert a due commitment directly via the store layer."""
    from cortex.commitments.store import create_commitment

    await create_commitment(
        storage=storage,
        owner_id=owner_id,
        content=content,
        due_at=due_at,
    )


async def _add_open_loop(storage, owner_id: str, content: str) -> None:
    """Insert an open loop directly via the store layer."""
    from cortex.commitments.store import create_open_loop

    await create_open_loop(
        storage=storage,
        owner_id=owner_id,
        content=content,
    )


async def _seal_cornerstone(plugin, label: str, content: str, owner_id: str) -> None:
    """Seal a cornerstone via the CornerstoneGuardian."""
    await plugin.cornerstone_guardian.seal(
        label=label,
        content=content,
        owner_id=owner_id,
    )


# ---------------------------------------------------------------------------
# W4: Empty boot (baseline)
# ---------------------------------------------------------------------------


class TestWakeEmptyBoot:
    """W4 — Wake on a fresh DB with no commitments, loops, or cornerstones."""

    @pytest.mark.asyncio
    async def test_empty_boot_returns_wake_report(self, plugin):
        """wake() always returns a WakeReport with a valid session_id."""
        report = await plugin.wake(session_id="w4-session-001")
        assert report.session_id == "w4-session-001"

    @pytest.mark.asyncio
    async def test_empty_boot_zero_cornerstones(self, plugin):
        """Empty DB → cornerstones_loaded == 0."""
        report = await plugin.wake(session_id="w4-session-002")
        assert report.cornerstones_loaded == 0

    @pytest.mark.asyncio
    async def test_empty_boot_zero_commitments(self, plugin):
        """Empty DB → due_commitments == 0."""
        report = await plugin.wake(session_id="w4-session-003")
        assert report.due_commitments == 0

    @pytest.mark.asyncio
    async def test_empty_boot_zero_open_loops(self, plugin):
        """Empty DB → open_loops == 0."""
        report = await plugin.wake(session_id="w4-session-004")
        assert report.open_loops == 0

    @pytest.mark.asyncio
    async def test_empty_boot_no_errors(self, plugin):
        """Clean DB should produce no wake errors."""
        report = await plugin.wake(session_id="w4-session-005")
        assert report.errors == [], f"Unexpected wake errors: {report.errors}"

    @pytest.mark.asyncio
    async def test_empty_boot_session_created_in_storage(self, plugin):
        """wake() must create a session record in storage."""
        report = await plugin.wake(session_id="w4-session-006")
        conn = await plugin.storage._get_conn()
        async with conn.execute(
            "SELECT session_id FROM session_record WHERE session_id = ?",
            (report.session_id,),
        ) as cur:
            row = await cur.fetchone()
        assert row is not None, "Session record not found in storage after wake()"


# ---------------------------------------------------------------------------
# W1: Boot with commitments
# ---------------------------------------------------------------------------


class TestWakeWithCommitments:
    """W1 — Wake surfaces due commitments in the WakeReport."""

    @pytest.mark.asyncio
    async def test_due_commitment_counted_in_wake_report(self, plugin):
        """Due commitment inserted before wake() appears in due_commitments count."""
        owner_id = plugin.config.owner_id
        # Use a past due_at so the commitment is already due
        await _add_commitment(
            plugin.storage,
            owner_id=owner_id,
            content="Finish the Cortex v1.2 release notes",
            due_at="2020-01-01T00:00:00",
        )

        report = await plugin.wake(session_id="w1-session-001")
        assert report.due_commitments == 1

    @pytest.mark.asyncio
    async def test_multiple_due_commitments_counted(self, plugin):
        """Multiple due commitments are all counted."""
        owner_id = plugin.config.owner_id
        for i in range(3):
            await _add_commitment(
                plugin.storage,
                owner_id=owner_id,
                content=f"Task {i}",
                due_at="2020-01-01T00:00:00",
            )

        report = await plugin.wake(session_id="w1-session-002")
        assert report.due_commitments == 3

    @pytest.mark.asyncio
    async def test_future_commitment_not_counted_as_due(self, plugin):
        """A commitment with a far-future due_at is NOT counted as due."""
        owner_id = plugin.config.owner_id
        await _add_commitment(
            plugin.storage,
            owner_id=owner_id,
            content="Far-future task",
            due_at="2099-12-31T23:59:59",
        )

        report = await plugin.wake(session_id="w1-session-003")
        assert report.due_commitments == 0

    @pytest.mark.asyncio
    async def test_commitment_with_no_due_at_not_counted(self, plugin):
        """A commitment with no due_at is not surfaced as 'due'."""
        owner_id = plugin.config.owner_id
        await _add_commitment(
            plugin.storage,
            owner_id=owner_id,
            content="Someday task with no deadline",
            due_at=None,  # no deadline
        )

        report = await plugin.wake(session_id="w1-session-004")
        # Commitments with no due_at are not overdue
        assert report.due_commitments == 0


# ---------------------------------------------------------------------------
# W2: Boot with open loops
# ---------------------------------------------------------------------------


class TestWakeWithOpenLoops:
    """W2 — Wake surfaces open loops in the WakeReport."""

    @pytest.mark.asyncio
    async def test_open_loop_counted_in_wake_report(self, plugin):
        """One open loop inserted before wake() appears in open_loops count."""
        owner_id = plugin.config.owner_id
        await _add_open_loop(
            plugin.storage,
            owner_id=owner_id,
            content="Follow up on the ElectricSheep API design",
        )

        report = await plugin.wake(session_id="w2-session-001")
        assert report.open_loops == 1

    @pytest.mark.asyncio
    async def test_multiple_open_loops_counted(self, plugin):
        """Multiple open loops are all counted."""
        owner_id = plugin.config.owner_id
        for i in range(4):
            await _add_open_loop(
                plugin.storage,
                owner_id=owner_id,
                content=f"Open loop {i}",
            )

        report = await plugin.wake(session_id="w2-session-002")
        assert report.open_loops == 4

    @pytest.mark.asyncio
    async def test_open_loops_and_commitments_combined(self, plugin):
        """Both open loops and due commitments appear in the same WakeReport."""
        owner_id = plugin.config.owner_id
        await _add_commitment(
            plugin.storage,
            owner_id=owner_id,
            content="Submit the Q1 report",
            due_at="2020-01-01T00:00:00",
        )
        await _add_open_loop(
            plugin.storage,
            owner_id=owner_id,
            content="Check in on the retrieval latency issue",
        )

        report = await plugin.wake(session_id="w2-session-003")
        assert report.due_commitments == 1
        assert report.open_loops == 1


# ---------------------------------------------------------------------------
# W3: Boot with cornerstones
# ---------------------------------------------------------------------------


class TestWakeWithCornerstones:
    """W3 — Wake loads sealed cornerstones and reports count in WakeReport."""

    @pytest.mark.asyncio
    async def test_cornerstone_loaded_in_wake_report(self, plugin):
        """One sealed cornerstone is loaded and counted on wake()."""
        owner_id = plugin.config.owner_id
        await _seal_cornerstone(
            plugin,
            label="Identity",
            content="I am Eva, the AI companion and engineering lead for 100Yen Org.",
            owner_id=owner_id,
        )

        report = await plugin.wake(session_id="w3-session-001")
        assert report.cornerstones_loaded == 1

    @pytest.mark.asyncio
    async def test_multiple_cornerstones_loaded(self, plugin):
        """Multiple sealed cornerstones are all loaded and counted."""
        owner_id = plugin.config.owner_id
        cornerstones = [
            ("Identity", "I am Eva."),
            ("Mission", "Build affordable AI tools."),
            ("Principle", "100Yen means simple, cheap, and reliable."),
        ]
        for label, content in cornerstones:
            await _seal_cornerstone(plugin, label=label, content=content, owner_id=owner_id)

        report = await plugin.wake(session_id="w3-session-002")
        assert report.cornerstones_loaded == len(cornerstones)

    @pytest.mark.asyncio
    async def test_cornerstones_accessible_after_wake(self, plugin):
        """After wake(), cornerstones are accessible via get_cornerstones()."""
        owner_id = plugin.config.owner_id
        await _seal_cornerstone(
            plugin,
            label="Mission",
            content="Always ship the smallest complete loop.",
            owner_id=owner_id,
        )

        await plugin.wake(session_id="w3-session-003")
        cornerstones = await plugin.get_cornerstones()
        assert len(cornerstones) == 1
        assert cornerstones[0].label == "Mission"

    @pytest.mark.asyncio
    async def test_wake_report_no_errors_with_cornerstones(self, plugin):
        """Cornerstone loading should not produce wake errors."""
        owner_id = plugin.config.owner_id
        await _seal_cornerstone(
            plugin,
            label="Core Value",
            content="Ship reliably, iterate fast.",
            owner_id=owner_id,
        )

        report = await plugin.wake(session_id="w3-session-004")
        assert report.errors == [], f"Unexpected wake errors: {report.errors}"

    @pytest.mark.asyncio
    async def test_wake_with_all_boot_items(self, plugin):
        """Full boot: cornerstones + due commitments + open loops all loaded."""
        owner_id = plugin.config.owner_id

        # Seal a cornerstone
        await _seal_cornerstone(
            plugin,
            label="Identity",
            content="I am Eva.",
            owner_id=owner_id,
        )
        # Add a due commitment
        await _add_commitment(
            plugin.storage,
            owner_id=owner_id,
            content="Review PR #346",
            due_at="2020-01-01T00:00:00",
        )
        # Add an open loop
        await _add_open_loop(
            plugin.storage,
            owner_id=owner_id,
            content="Follow up on wake pipeline tests",
        )

        report = await plugin.wake(session_id="w3-session-005")
        assert report.cornerstones_loaded == 1
        assert report.due_commitments == 1
        assert report.open_loops == 1
        assert report.errors == []


# ---------------------------------------------------------------------------
# Retrieval setup after wake (integration sanity)
# ---------------------------------------------------------------------------


class TestRetrievalAfterWake:
    """Verify retrieval pipeline is functional after wake()."""

    @pytest.mark.asyncio
    async def test_retrieve_works_after_wake(self, plugin):
        """retrieve() returns a valid RetrievalResult after wake()."""
        from cortex.core.retrieval import RetrievalConstraints

        await plugin.wake(session_id="retrieval-session-001")

        result = await plugin.retrieval.retrieve(
            "test query",
            mode="lightweight",
            constraints=RetrievalConstraints(token_budget=1000),
            owner_id=plugin.config.owner_id,
        )
        # On empty DB retrieval returns no items but should not crash
        assert result is not None
        assert isinstance(result.items, list)
        assert result.mode == "lightweight"

    @pytest.mark.asyncio
    async def test_invalid_retrieval_mode_raises_after_wake(self, plugin):
        """Invalid mode raises ValueError even after a successful wake() (#346)."""
        await plugin.wake(session_id="retrieval-session-002")

        with pytest.raises(ValueError, match="Invalid retrieval mode"):
            await plugin.retrieval.retrieve(
                "test query",
                mode="nonexistent_mode",
                owner_id=plugin.config.owner_id,
            )
