"""
Integration tests for HindsightEmbedded client.

Tests the embedded client with automatic server lifecycle management:
1. Lazy server startup on first use
2. Server reuse across multiple operations
3. Context manager support
4. Method proxying to underlying HindsightClient
5. Proper cleanup

Note: Each test uses random bank_ids to avoid conflicts and allow safe parallel execution.
"""

import os
import uuid

import pytest
import urllib.request
import json

from hindsight import HindsightEmbedded


@pytest.fixture(scope="session")
def llm_config():
    """Get LLM configuration from environment (session-scoped)."""
    # Try both naming conventions
    provider = os.getenv("HINDSIGHT_API_LLM_PROVIDER") or os.getenv(
        "HINDSIGHT_LLM_PROVIDER", "groq"
    )
    api_key = os.getenv("HINDSIGHT_API_LLM_API_KEY") or os.getenv(
        "HINDSIGHT_LLM_API_KEY", ""
    )
    model = os.getenv("HINDSIGHT_API_LLM_MODEL") or os.getenv(
        "HINDSIGHT_LLM_MODEL", "openai/gpt-oss-120b"
    )

    if not api_key:
        pytest.skip(
            "LLM API key not configured. Set HINDSIGHT_API_LLM_API_KEY or HINDSIGHT_LLM_API_KEY."
        )

    return {
        "llm_provider": provider,
        "llm_api_key": api_key,
        "llm_model": model,
    }


def test_embedded_lazy_start(llm_config):
    """
    Test that HindsightEmbedded starts server lazily on first use.
    """
    profile = f"test_lazy_{uuid.uuid4().hex[:8]}"
    bank_id = f"bank_{uuid.uuid4().hex[:8]}"

    # Create client - should NOT start server yet
    client = HindsightEmbedded(profile=profile, log_level="info", **llm_config)
    assert not client.is_running, "Server should not be running after initialization"

    # First call should start server
    result = client.retain(bank_id=bank_id, content="Test content for lazy start")

    # Verify server is now running
    assert client.is_running, "Server should be running after first call"
    assert result.success, "Retain should succeed"
    assert result.items_count >= 1, "Should have stored at least 1 item"

    # Cleanup
    client.close()
    assert not client.is_running, "Server should stop after close()"


def test_embedded_context_manager(llm_config):
    """
    Test HindsightEmbedded with context manager.
    """
    profile = f"test_ctx_{uuid.uuid4().hex[:8]}"
    bank_id = f"bank_{uuid.uuid4().hex[:8]}"

    # Use context manager
    with HindsightEmbedded(profile=profile, log_level="info", **llm_config) as client:
        assert client.is_running, "Server should be running inside context"

        # Store memory
        result = client.retain(bank_id=bank_id, content="Testing context manager")
        assert result.success, "Retain should succeed"

        # Recall memory
        recall_results = client.recall(bank_id=bank_id, query="context")
        assert isinstance(recall_results.results, list), (
            "Recall should return results list"
        )

    # Server should be stopped after context exit
    # Note: We can't check client.is_running here as client is out of scope


def test_embedded_complete_workflow(llm_config):
    """
    Test complete workflow with HindsightEmbedded.

    This test:
    1. Creates a client with lazy start
    2. Creates a memory bank
    3. Stores multiple memories
    4. Recalls memories
    5. Reflects on memories
    6. Tests cleanup
    """
    profile = f"test_workflow_{uuid.uuid4().hex[:8]}"
    bank_id = f"assistant_{uuid.uuid4().hex[:8]}"

    client = HindsightEmbedded(profile=profile, log_level="info", **llm_config)

    try:
        # Step 1: Create a memory bank
        print(f"\n1. Creating memory bank: {bank_id}")
        bank_response = client.create_bank(
            bank_id=bank_id,
            name="Test Assistant",
            mission="Help with programming tasks",
        )
        assert bank_response.bank_id == bank_id

        # Step 2: Store memories (single)
        print("\n2. Storing single memory...")
        retain_response = client.retain(
            bank_id=bank_id,
            content="User prefers Python for data analysis.",
            context="Programming preferences",
        )
        assert retain_response.success
        assert retain_response.items_count >= 1

        # Step 3: Store batch memories
        print("\n3. Storing batch memories...")
        batch_response = client.retain_batch(
            bank_id=bank_id,
            items=[
                {"content": "User works with pandas and numpy."},
                {"content": "User likes matplotlib for visualization."},
                {
                    "content": "User is interested in machine learning with scikit-learn."
                },
            ],
        )
        assert batch_response.success
        assert batch_response.items_count >= 3

        # Step 4: Recall memories
        print("\n4. Recalling memories...")
        recall_response = client.recall(
            bank_id=bank_id, query="What tools does the user prefer?", max_tokens=2000
        )
        assert isinstance(recall_response.results, list)
        assert len(recall_response.results) > 0
        print(f"   Found {len(recall_response.results)} relevant memories")

        # Step 5: Reflect on memories
        print("\n5. Reflecting on memories...")
        reflect_response = client.reflect(
            bank_id=bank_id,
            query="What programming tools should I recommend?",
            budget="low",
        )
        assert reflect_response.text
        assert len(reflect_response.text) > 0
        print(f"   Answer: {reflect_response.text[:150]}...")

        # Verify answer mentions relevant tools
        answer_lower = reflect_response.text.lower()
        assert any(
            term in answer_lower for term in ["python", "pandas", "numpy", "data"]
        )

        # Step 6: List memories
        print("\n6. Listing memories...")
        list_response = client.list_memories(bank_id=bank_id, limit=10)
        assert len(list_response.items) > 0
        print(f"   Listed {len(list_response.items)} memories")

    finally:
        # Cleanup
        client.close()


def test_embedded_server_reuse(llm_config):
    """
    Test that the same server is reused across multiple calls.
    """
    profile = f"test_reuse_{uuid.uuid4().hex[:8]}"
    bank_id = f"bank_{uuid.uuid4().hex[:8]}"

    client = HindsightEmbedded(profile=profile, log_level="info", **llm_config)

    try:
        # First call starts server
        result1 = client.retain(bank_id=bank_id, content="First message")
        url1 = client.url
        assert client.is_running

        # Second call should reuse the same server
        result2 = client.retain(bank_id=bank_id, content="Second message")
        url2 = client.url

        # URLs should be identical (same server)
        assert url1 == url2, "Server URL should remain the same across calls"
        assert result1.success and result2.success

        # Third call should also reuse
        recall_result = client.recall(bank_id=bank_id, query="message")
        url3 = client.url
        assert url3 == url1, "Server URL should remain the same for recall"
        assert isinstance(recall_result.results, list)

    finally:
        client.close()


def test_embedded_method_proxying(llm_config):
    """
    Test that all HindsightClient methods are properly proxied.

    This ensures __getattr__ proxying works for various method types.
    """
    profile = f"test_proxy_{uuid.uuid4().hex[:8]}"
    bank_id = f"bank_{uuid.uuid4().hex[:8]}"

    client = HindsightEmbedded(profile=profile, log_level="info", **llm_config)

    try:
        # Test bank operations
        bank = client.create_bank(bank_id=bank_id, name="Proxy Test")
        assert bank.bank_id == bank_id

        # Test mission setting
        mission_response = client.set_mission(
            bank_id=bank_id, mission="Test mission for proxying"
        )
        assert mission_response.bank_id == bank_id

        # Test retain
        retain_result = client.retain(bank_id=bank_id, content="Test content")
        assert retain_result.success

        # Test retain_batch
        batch_result = client.retain_batch(
            bank_id=bank_id, items=[{"content": "Item 1"}, {"content": "Item 2"}]
        )
        assert batch_result.success
        assert batch_result.items_count >= 2

        # Test recall
        recall_result = client.recall(bank_id=bank_id, query="test")
        assert hasattr(recall_result, "results")

        # Test reflect
        reflect_result = client.reflect(bank_id=bank_id, query="What is stored?")
        assert hasattr(reflect_result, "text")

        # Test list_memories
        list_result = client.list_memories(bank_id=bank_id, limit=5)
        assert hasattr(list_result, "items")

        print("✓ All methods successfully proxied")

    finally:
        client.close()


def test_embedded_multiple_banks(llm_config):
    """
    Test that HindsightEmbedded can work with multiple banks.
    """
    profile = f"test_multibank_{uuid.uuid4().hex[:8]}"
    bank1_id = f"bank1_{uuid.uuid4().hex[:8]}"
    bank2_id = f"bank2_{uuid.uuid4().hex[:8]}"

    client = HindsightEmbedded(profile=profile, log_level="info", **llm_config)

    try:
        # Create first bank and store data
        client.create_bank(bank_id=bank1_id, name="Bank 1")
        client.retain(bank_id=bank1_id, content="Alice prefers Python for data science")

        # Create second bank and store data
        client.create_bank(bank_id=bank2_id, name="Bank 2")
        client.retain(
            bank_id=bank2_id, content="Bob uses JavaScript for web development"
        )

        # Recall from both banks
        results1 = client.recall(bank_id=bank1_id, query="programming language")
        results2 = client.recall(bank_id=bank2_id, query="programming language")

        assert len(results1.results) > 0
        assert len(results2.results) > 0

        # Verify banks are isolated (each should only see their own content)
        # This is a basic check - content isolation is tested more thoroughly in other tests
        assert results1.results[0].text != results2.results[0].text or len(
            results1.results
        ) != len(results2.results)

    finally:
        client.close()


def test_embedded_profile_isolation(llm_config):
    """
    Test that different profiles create isolated data stores.
    """
    profile1 = f"test_iso1_{uuid.uuid4().hex[:8]}"
    profile2 = f"test_iso2_{uuid.uuid4().hex[:8]}"
    bank_id = "shared_bank_name"  # Same bank_id in both profiles

    client1 = HindsightEmbedded(profile=profile1, log_level="info", **llm_config)
    client2 = HindsightEmbedded(profile=profile2, log_level="info", **llm_config)

    try:
        # Store data in profile1
        client1.retain(
            bank_id=bank_id, content="User likes TypeScript for frontend development"
        )

        # Store different data in profile2
        client2.retain(
            bank_id=bank_id, content="User prefers Rust for systems programming"
        )

        # Each profile should only see its own data
        results1 = client1.recall(bank_id=bank_id, query="programming preference")
        results2 = client2.recall(bank_id=bank_id, query="programming preference")

        # Both should have results
        assert len(results1.results) > 0
        assert len(results2.results) > 0

        # Results should be different (basic isolation check)
        # Note: This is a basic sanity check. Full isolation is ensured by pg0's data directory separation

    finally:
        client1.close()
        client2.close()


def test_embedded_error_after_close(llm_config):
    """
    Test that using HindsightEmbedded after close() raises an error.
    """
    profile = f"test_error_{uuid.uuid4().hex[:8]}"
    bank_id = f"bank_{uuid.uuid4().hex[:8]}"

    client = HindsightEmbedded(profile=profile, log_level="info", **llm_config)

    # Use it once to start server
    client.retain(bank_id=bank_id, content="Test")

    # Close the client
    client.close()
    assert not client.is_running

    # Trying to use it after close should raise an error
    with pytest.raises(
        RuntimeError, match="Cannot use HindsightEmbedded after it has been closed"
    ):
        client.retain(bank_id=bank_id, content="This should fail")


def test_embedded_ui_flag(llm_config):
    """
    Test that ui=True starts the control plane UI alongside the daemon,
    and that the UI's health endpoint reports a connected dataplane.
    """
    profile = f"test_ui_{uuid.uuid4().hex[:8]}"
    bank_id = f"bank_{uuid.uuid4().hex[:8]}"

    client = HindsightEmbedded(profile=profile, log_level="info", ui=True, **llm_config)

    try:
        # First use triggers daemon + UI startup
        result = client.retain(bank_id=bank_id, content="UI integration test content")
        assert result.success, "Retain should succeed"
        assert client.is_running, "Daemon should be running"

        # Verify UI is reachable and reports connected dataplane
        ui_url = client.ui_url
        assert ui_url, "ui_url should be set"

        health_url = f"{ui_url}/api/health"
        with urllib.request.urlopen(health_url, timeout=10) as resp:
            health = json.loads(resp.read().decode())

        assert health["status"] == "ok", (
            f"UI health status should be 'ok', got: {health['status']}"
        )
        assert health["dataplane"]["status"] == "connected", (
            f"Dataplane should be connected, got: {health['dataplane']}"
        )

    finally:
        client.close()
