import json
import pytest
import sys
from pathlib import Path

# Add parent of db_core to path so we can import db_core as a package
parent_dir = Path(__file__).parent.parent.parent
if str(parent_dir) not in sys.path:
    sys.path.insert(0, str(parent_dir))

from db_core.clients.elasticsearch import ElasticsearchClient
from db_core.query import Query

pytestmark = pytest.mark.elastic


@pytest.mark.asyncio
async def test_connection(es_client: ElasticsearchClient):
    """Test cluster connection"""
    assert es_client.client is not None


@pytest.mark.asyncio
async def test_disconnect(es_client: ElasticsearchClient):
    """Test that a query works (connection is alive)"""
    result = await es_client.table("users").limit(1).get()
    assert result.count() == 1


@pytest.mark.asyncio
async def test_raw_query(es_client: ElasticsearchClient):
    """Test raw query generation returns valid JSON body"""
    query = Query(table="users", selects=["name", "email"])
    json_body, params = es_client.raw_query(query)

    body = json.loads(json_body)
    assert "query" in body
    assert isinstance(params, dict)


@pytest.mark.asyncio
async def test_raw_query_with_where(es_client: ElasticsearchClient):
    """Test raw query generation with a filter condition"""
    qb = es_client.table("users").where("age", ">", 25)
    json_body, _ = es_client.raw_query(qb.query)

    body = json.loads(json_body)
    assert "query" in body
    assert "bool" in body["query"]
    assert "must" in body["query"]["bool"]


@pytest.mark.asyncio
async def test_explain(es_client: ElasticsearchClient):
    """Test explain functionality via validate API"""
    query = Query(table="users")
    explain_output = await es_client.explain(query)

    assert explain_output is not None
    assert len(explain_output) > 0
    parsed = json.loads(explain_output)
    assert "valid" in parsed


@pytest.mark.asyncio
async def test_explain_analyze(es_client: ElasticsearchClient):
    """Test explain with analyze=True (profile mode)"""
    query = Query(table="users")
    explain_output = await es_client.explain(query, analyze=True)

    assert explain_output is not None
    assert len(explain_output) > 0
    parsed = json.loads(explain_output)
    assert "profile" in parsed


@pytest.mark.asyncio
async def test_table_method(es_client: ElasticsearchClient):
    """Test table method returns a QueryBuilder"""
    qb = es_client.table("users")
    assert qb is not None
    assert qb.query.table == "users"
    assert hasattr(qb, "where")
    assert hasattr(qb, "select")
    assert hasattr(qb, "get")


@pytest.mark.asyncio
async def test_execute_simple_query(es_client: ElasticsearchClient):
    """Test executing a simple query returns all seeded documents"""
    query = Query(table="users", selects=["*"])
    result = await es_client._execute(query)

    assert result is not None
    assert result.count() == 4


@pytest.mark.asyncio
async def test_execute_query_with_limit(es_client: ElasticsearchClient):
    """Test executing a query with a size limit"""
    query = Query(table="users", selects=["*"], limit=2)
    result = await es_client._execute(query)

    assert result.count() == 2


@pytest.mark.asyncio
async def test_find_one_and_find_many(es_client: ElasticsearchClient):
    """Test DBClient convenience methods find_one and find_many"""
    alice = await es_client.find_one("users", {"email": "alice@example.com"})
    assert alice is not None
    assert alice["name"] == "Alice Smith"
    assert alice["age"] == 30

    missing = await es_client.find_one("users", {"email": "missing@example.com"})
    assert missing is None

    users = await es_client.find_many("users", {"age": 25})
    assert isinstance(users, list)
    assert len(users) == 1
    assert users[0]["name"] == "Bob Jones"

    top_two = await es_client.find_many("users", limit=2, order_by=["-age"])
    assert len(top_two) == 2
    assert top_two[0]["age"] >= top_two[1]["age"]


@pytest.mark.asyncio
async def test_where_operator_gt(es_client: ElasticsearchClient):
    """Test where with > operator"""
    result = await es_client.table("users").where("age", ">", 28).get()
    assert result.count() == 2


@pytest.mark.asyncio
async def test_where_gte(es_client: ElasticsearchClient):
    """Test where_gte helper"""
    result = await es_client.table("users").where_gte("age", 30).get()
    assert result.count() == 2


@pytest.mark.asyncio
async def test_where_in(es_client: ElasticsearchClient):
    """Test where_in helper"""
    result = await es_client.table("users").where_in("email", [
        "alice@example.com", "bob@example.com"
    ]).get()
    assert result.count() == 2


@pytest.mark.asyncio
async def test_where_not_eq(es_client: ElasticsearchClient):
    """Test where with != operator"""
    result = await es_client.table("users").where("age", "!=", 30).get()
    assert result.count() == 3


@pytest.mark.asyncio
async def test_where_nested_conditions(es_client: ElasticsearchClient):
    """Test nested Elasticsearch query generation and execution."""
    index = "nested_users"
    mapping = {
        "mappings": {
            "properties": {
                "id": {"type": "integer"},
                "name": {"type": "text", "fields": {"keyword": {"type": "keyword"}}},
                "comments": {
                    "type": "nested",
                    "properties": {
                        "author": {
                            "type": "nested",
                            "properties": {
                                "name": {"type": "keyword"},
                                "age": {"type": "integer"},
                            },
                        },
                        "message": {"type": "text"},
                    },
                },
            }
        }
    }
    docs = [
        {
            "id": 1,
            "name": "Alice Smith",
            "comments": [
                {"author": {"name": "Alice", "age": 30}, "message": "Hello world"},
                {"author": {"name": "Bob", "age": 40}, "message": "Second comment"},
            ],
        },
        {
            "id": 2,
            "name": "Bob Jones",
            "comments": [
                {"author": {"name": "Charlie", "age": 25}, "message": "Another comment"},
            ],
        },
    ]

    raw = es_client.client
    if await raw.indices.exists(index=index):
        await raw.indices.delete(index=index)
    await raw.indices.create(index=index, **mapping)
    for doc in docs:
        await raw.index(index=index, id=str(doc["id"]), document=doc)
    await raw.indices.refresh(index=index)

    try:
        query = es_client.table(index).where_nested("comments.author", lambda q: q.where("comments.author.name", "Alice").where_gt("comments.author.age", 25))
        json_body, _ = es_client.raw_query(query.query)
        body = json.loads(json_body)

        assert body["query"]["bool"]["must"][0]["nested"]["path"] == "comments.author"
        assert body["query"]["bool"]["must"][0]["nested"]["query"]["bool"]["must"]

        result = await query.get()
        assert result.count() == 1
        assert result.first()["name"] == "Alice Smith"
    finally:
        await raw.indices.delete(index=index)


@pytest.mark.asyncio
async def test_order_by(es_client: ElasticsearchClient):
    """Test ordering results"""
    result = await es_client.table("users").order_by("-age").get()
    rows = result.all()
    assert rows[0]["age"] == 35
    assert rows[-1]["age"] == 25


@pytest.mark.asyncio
async def test_offset(es_client: ElasticsearchClient):
    """Test offset (from_) skips documents"""
    all_result = await es_client.table("users").order_by("id").get()
    offset_result = await es_client.table("users").order_by("id").offset(2).get()

    assert offset_result.count() == 2
    assert offset_result.first()["id"] == all_result.all()[2]["id"]


@pytest.mark.asyncio
async def test_client_settings(es_client: ElasticsearchClient):
    """Test that the underlying AsyncElasticsearch client is configured"""
    assert es_client.client is not None
    info = await es_client.client.info()
    assert "version" in info
