"""Integration tests for HANASemanticLLMCache against a real HANA Cloud instance.

These tests require a running HANA Cloud instance. They are skipped
automatically when the HANA_HOST environment variable is not set.

Set the following environment variables (or use a .env file):
    HANA_HOST, HANA_PORT, HANA_USER, HANA_PASSWORD
"""

from __future__ import annotations

import os
import uuid

import pytest
from langchain_core.outputs import Generation

pytestmark = pytest.mark.skipif(
    not os.environ.get("HANA_HOST"),
    reason="HANA_HOST not set — skipping integration tests",
)


@pytest.fixture(scope="module")
def hana_connection():
    """Create a real HANA connection from environment variables."""
    from hdbcli import dbapi

    conn = dbapi.connect(
        address=os.environ["HANA_HOST"],
        port=int(os.environ.get("HANA_PORT", "443")),
        user=os.environ["HANA_USER"],
        password=os.environ["HANA_PASSWORD"],
        encrypt=True,
    )
    yield conn
    conn.close()


@pytest.fixture(scope="module")
def test_table_name():
    """Generate a unique table name for this test run."""
    return f"LLM_CACHE_TEST_{uuid.uuid4().hex[:8].upper()}"


@pytest.fixture(scope="module")
def embedding_model():
    """Create an OpenAI embedding model for integration tests."""
    from langchain_openai import OpenAIEmbeddings

    return OpenAIEmbeddings(model="text-embedding-3-small")


@pytest.fixture(scope="module")
def cache(hana_connection, embedding_model, test_table_name):
    """Create a cache instance with a unique test table."""
    from langchain_hana_cache import HANASemanticLLMCache

    c = HANASemanticLLMCache(
        connection=hana_connection,
        embedding=embedding_model,
        table_name=test_table_name,
        similarity_threshold=0.90,
        ttl_seconds=3600,
    )
    yield c
    # Cleanup: drop test table
    cur = hana_connection.cursor()
    try:
        cur.execute(f'DROP TABLE "{test_table_name}"')
        hana_connection.commit()
    except Exception:
        pass
    finally:
        cur.close()


class TestConnection:

    def test_table_created(self, cache, hana_connection, test_table_name):
        cur = hana_connection.cursor()
        try:
            cur.execute(
                "SELECT COUNT(*) FROM SYS.TABLES "
                "WHERE SCHEMA_NAME = CURRENT_SCHEMA AND TABLE_NAME = ?",
                (test_table_name,),
            )
            row = cur.fetchone()
            assert row[0] == 1
        finally:
            cur.close()


class TestRoundTrip:

    def test_store_and_lookup_exact(self, cache):
        """Store a response and look it up with the exact same prompt."""
        prompt = "What is the capital of France?"
        llm_string = "gpt-4o-test"
        generations = [Generation(text="The capital of France is Paris.")]

        cache.update(prompt, llm_string, generations)

        result = cache.lookup(prompt, llm_string)
        assert result is not None
        assert len(result) == 1
        assert "Paris" in result[0].text

    def test_semantic_cache_hit(self, cache):
        """A semantically similar prompt should hit the cache."""
        prompt1 = "Explain what photosynthesis is in simple terms"
        llm_string = "gpt-4o-test"
        generations = [Generation(text="Photosynthesis is how plants make food from sunlight.")]

        cache.update(prompt1, llm_string, generations)

        # Similar prompt — should match above threshold
        prompt2 = "Tell me about photosynthesis in simple words"
        result = cache.lookup(prompt2, llm_string)
        assert result is not None
        assert "Photosynthesis" in result[0].text

    def test_different_llm_string_is_miss(self, cache):
        """Same prompt but different LLM_STRING should not match."""
        prompt = "What is quantum computing?"
        generations = [Generation(text="Quantum computing uses qubits.")]

        cache.update(prompt, "gpt-4o-test", generations)

        result = cache.lookup(prompt, "gpt-3.5-turbo-test")
        assert result is None

    def test_unrelated_prompt_is_miss(self, cache):
        """A completely unrelated prompt should not match."""
        prompt = "What is the recipe for chocolate cake?"
        llm_string = "gpt-4o-test"
        generations = [Generation(text="Mix flour, sugar, cocoa...")]

        cache.update(prompt, llm_string, generations)

        result = cache.lookup("How do airplane engines work?", llm_string)
        assert result is None


class TestClear:

    def test_clear_removes_all(self, cache):
        """clear() should remove all entries."""
        cache.update("prompt1", "gpt-4o-test", [Generation(text="r1")])
        cache.update("prompt2", "gpt-4o-test", [Generation(text="r2")])

        cache.clear()

        assert cache.lookup("prompt1", "gpt-4o-test") is None
        assert cache.lookup("prompt2", "gpt-4o-test") is None


class TestEviction:

    def test_evict_lru(self, cache):
        """evict_lru should keep only the most recently accessed entries."""
        for i in range(5):
            cache.update(f"lru prompt {i}", "gpt-4o-test", [Generation(text=f"r{i}")])

        deleted = cache.evict_lru(max_entries=2)
        assert deleted == 3
