"""
Real LLM + Qdrant Integration Test
Tests the extreme learning behavior of the autonomous engine by hitting
the real local Ollama instance and storing/retrieving from local Qdrant.

Requirements:
- Ollama must be running on localhost:11434
- llama3.2-vision must be available locally
- Qdrant must be running locally
"""

import time
import uuid
from unittest.mock import MagicMock, patch

import pytest

from GramAddict.core.device_facade import DeviceFacade
from GramAddict.core.situational_awareness import SituationalAwarenessEngine, SituationType


def make_mock_device(app_id="com.instagram.android"):
    device = MagicMock(spec=DeviceFacade)
    device.app_id = app_id
    device.deviceV2 = MagicMock()
    device.dump_hierarchy = MagicMock()
    device.click = MagicMock()
    device.press = MagicMock()
    device.app_start = MagicMock()
    device._trace_counter = 0
    device._trace_dir = "/tmp/test_traces"
    return device


# ─────────────────────────────────────────────────────
# Tests
# ─────────────────────────────────────────────────────


@pytest.mark.live_llm
def test_real_llm_learning_and_unlearning(isolated_screen_memory):
    """
    Testet das echte Lernverhalten:
    1. Pass: Unbekanntes XML -> LLM wird angefragt -> Speichert in Qdrant
    2. Pass: Gleiches XML -> LLM wird NICHT angefragt -> Holt aus Qdrant
    3. Pass (Unlearn): Wir löschen den State (Simulation Fehler) -> Gleiches XML -> LLM wird wieder angefragt
    """

    # Check if Qdrant is connected. If not, we skip the test gracefully.
    if not isolated_screen_memory.is_connected:
        pytest.skip("Qdrant is not running locally. Skipping live integration test.")

    # Generate completely unique XML so it's guaranteed NOT in any cache
    random_id = f"com.instagram.android:id/chaos_{uuid.uuid4().hex[:8]}"
    random_text = f"REAL_LLM_TEST_{uuid.uuid4().hex[:8]}"

    # A simple modal to trigger perception
    chaos_xml = f"""<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
  <node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.instagram.android" content-desc="" clickable="false" bounds="[0,0][1080,2400]">
    <node text="" resource-id="{random_id}" class="android.widget.FrameLayout" package="com.instagram.android" clickable="false" bounds="[0,500][1080,2200]">
      <node text="{random_text}" resource-id="" class="android.widget.TextView" package="com.instagram.android" clickable="false" bounds="[100,600][980,700]" />
      <node text="Dismiss" resource-id="" class="android.widget.Button" package="com.instagram.android" clickable="true" bounds="[100,2000][540,2100]" />
    </node>
  </node>
</hierarchy>"""

    device = make_mock_device()
    sae = SituationalAwarenessEngine(device)

    # We patch the underlying LLM call just to spy on it (wraps the original function)
    from GramAddict.core.llm_provider import query_telepathic_llm

    with patch(
        "GramAddict.core.llm_provider.query_telepathic_llm", autospec=True, wraps=query_telepathic_llm
    ) as spy_llm:
        # ---------------------------------------------------------
        # PASS 1: The Initial Encounter (Learn)
        # ---------------------------------------------------------
        print(f"\n--- PASS 1: Querying real LLM for '{random_text}' ---")
        start_time = time.time()

        # This will block and hit the real local Ollama
        result_pass1 = sae.perceive(chaos_xml)

        duration = time.time() - start_time
        print(f"Pass 1 completed in {duration:.2f}s. Result: {result_pass1}")

        # Assertions
        assert spy_llm.call_count == 1, "LLM was not called on unknown XML!"
        assert result_pass1 in [
            SituationType.OBSTACLE_MODAL,
            SituationType.NORMAL,
            SituationType.OBSTACLE_FOREIGN_APP,
        ], "Invalid LLM perception result"

        spy_llm.reset_mock()

        # Give Qdrant a split second to index the new point
        time.sleep(0.5)

        # ---------------------------------------------------------
        # PASS 2: The Recall (Cache Hit)
        # ---------------------------------------------------------
        print("\\n--- PASS 2: Recalling from Qdrant ---")
        start_time = time.time()

        result_pass2 = sae.perceive(chaos_xml)

        duration = time.time() - start_time
        print(f"Pass 2 completed in {duration:.2f}s. Result: {result_pass2}")

        # Assertions
        assert spy_llm.call_count == 0, "LLM was called again despite being in Qdrant!"
        assert result_pass2 == result_pass1, "Qdrant cache returned a different result than the initial LLM call!"
        assert duration < 1.0, f"Qdrant retrieval took too long ({duration:.2f}s). Should be sub-second."

        # ---------------------------------------------------------
        # PASS 3: The Unlearn (Mistake Recovery)
        # ---------------------------------------------------------
        print("\\n--- PASS 3: Unlearning and verifying re-query ---")

        # We simulate that the bot decided this classification was wrong and unlearns it
        sae.unlearn_current_state(chaos_xml)

        # Give Qdrant a split second to process the deletion
        time.sleep(0.5)

        start_time = time.time()
        result_pass3 = sae.perceive(chaos_xml)
        duration = time.time() - start_time

        print(f"Pass 3 completed in {duration:.2f}s. Result: {result_pass3}")

        # Assertions
        assert spy_llm.call_count == 1, "LLM was NOT called after unlearning! Qdrant deletion failed."
        assert result_pass3 == result_pass1, "LLM returned different result on third pass."

        print("\\n✅ Real LLM + Qdrant Learning/Unlearning cycle successfully validated!")
