"""
E2E tests for all Shaped MCP server capabilities via direct Python calls.

Exercises every tool, resource, and prompt function against the real Shaped API
— no transport layer involved. This is the authoritative correctness test:
CRUD lifecycles, error handling, resource content, and prompt rendering.
The transport smoke tests (test_stdio_transport_e2e.py,
test_http_transport_e2e.py) rely on this file for correctness coverage and only
verify that their respective transports work end-to-end.

All tests are @pytest.mark.integration and skip without SHAPED_API_KEY. Uses
demo_data fixtures from the repo root for CRUD operations with automatic cleanup.

Run with: pytest tests/ -v -m integration
"""

import json
import os
import uuid
from pathlib import Path

import pytest
from shaped_mcp.server import (
    account_resource,
    docs_shapedql_resource,
    engine_resource,
    shaped_build_query_prompt,
    shaped_create_engine,
    shaped_create_table,
    shaped_create_view,
    shaped_delete_engine,
    shaped_delete_table,
    shaped_delete_view,
    shaped_fetch_docs,
    shaped_get_table,
    shaped_get_view,
    shaped_global_query,
    shaped_insert_rows,
    shaped_list_engines,
    shaped_list_tables,
    shaped_list_views,
    shaped_validate_engine_config,
)


def _has_api_key():
    return bool(os.environ.get("SHAPED_API_KEY"))


def _demo_dir() -> Path:
    for parent in Path(__file__).resolve().parents:
        candidate = parent / "demo_data"
        if candidate.is_dir():
            return candidate
    raise RuntimeError("demo_data/ not found")


@pytest.fixture
def unique_id():
    return uuid.uuid4().hex[:8]


@pytest.fixture
def ci_table_name(unique_id):
    name = f"ci_mcp_table_{unique_id}"
    yield name
    try:
        shaped_delete_table(name)
    except Exception:
        pass


@pytest.fixture
def ci_view_name(unique_id):
    name = f"ci_mcp_view_{unique_id}"
    yield name
    try:
        shaped_delete_view(name)
    except Exception:
        pass


@pytest.fixture
def ci_engine_name(unique_id):
    name = f"ci_mcp_engine_{unique_id}"
    yield name
    try:
        shaped_delete_engine(name)
    except Exception:
        pass


# ---------------------------------------------------------------------------
# Config validation
# ---------------------------------------------------------------------------


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_shaped_validate_engine_config_valid_integration():
    """Valid v2 engine config returns valid: true."""
    demo_dir = _demo_dir()
    config = json.loads(
        (demo_dir / "engines" / "pixar_movies_items_only.json").read_text()
    )
    result = shaped_validate_engine_config(json.dumps(config))
    data = json.loads(result)
    assert data.get("valid") is True, f"Expected valid=true, got: {data}"


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_shaped_validate_engine_config_invalid_integration():
    """Invalid engine config returns valid: false."""
    bad_config = {"name": "bad_engine", "version": "v2", "data": "not_an_object"}
    result = shaped_validate_engine_config(json.dumps(bad_config))
    data = json.loads(result)
    assert data.get("valid") is False, f"Expected valid=false, got: {data}"


# ---------------------------------------------------------------------------
# Docs
# ---------------------------------------------------------------------------


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_shaped_fetch_docs_integration():
    """shaped_fetch_docs returns non-empty content for a known slug."""
    result = shaped_fetch_docs("shapedql")
    assert result, "Expected non-empty docs content"
    assert "error" not in result.lower() or len(result) > 100


# ---------------------------------------------------------------------------
# Table CRUD
# ---------------------------------------------------------------------------


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_shaped_table_crud_integration(ci_table_name):
    """Create, get, insert rows into, and delete a table."""
    demo_dir = _demo_dir()
    table_config = json.loads(
        (demo_dir / "tables" / "items" / "pixar.json").read_text()
    )
    table_config["name"] = ci_table_name

    # Create
    result = shaped_create_table(json.dumps(table_config))
    data = json.loads(result)
    assert "error" not in data, f"Create table error: {data.get('error')}"

    # Get
    result = shaped_get_table(ci_table_name)
    data = json.loads(result)
    assert data.get("name") == ci_table_name, f"Unexpected table: {data}"

    # Insert rows
    rows_path = demo_dir / "data" / "items" / "pixar.jsonl"
    rows = [
        json.loads(line) for line in rows_path.read_text().splitlines() if line.strip()
    ]
    result = shaped_insert_rows(ci_table_name, json.dumps(rows))
    data = json.loads(result)
    assert "error" not in data, f"Insert rows error: {data.get('error')}"


# ---------------------------------------------------------------------------
# View CRUD
# ---------------------------------------------------------------------------


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_shaped_view_crud_integration(ci_table_name, ci_view_name):
    """Create, get, and delete a view (requires the source table to exist)."""
    demo_dir = _demo_dir()

    # Create the backing table first
    table_config = json.loads(
        (demo_dir / "tables" / "items" / "pixar.json").read_text()
    )
    table_config["name"] = ci_table_name
    shaped_create_table(json.dumps(table_config))

    # Create view
    view_config = json.loads((demo_dir / "views" / "items" / "pixar.json").read_text())
    view_config["name"] = ci_view_name
    view_config["source_dataset"] = ci_table_name
    result = shaped_create_view(json.dumps(view_config))
    data = json.loads(result)
    assert "error" not in data, f"Create view error: {data.get('error')}"

    # Get view
    result = shaped_get_view(ci_view_name)
    data = json.loads(result)
    assert data.get("name") == ci_view_name, f"Unexpected view: {data}"

    # Delete view (fixture also cleans up, but test it explicitly)
    result = shaped_delete_view(ci_view_name)
    data = json.loads(result)
    assert "error" not in data, f"Delete view error: {data.get('error')}"


# ---------------------------------------------------------------------------
# Engine CRUD
# ---------------------------------------------------------------------------


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_shaped_engine_create_delete_integration(ci_table_name, ci_engine_name):
    """Create and immediately delete an engine (API round-trip only)."""
    demo_dir = _demo_dir()

    # Create the backing table first
    table_config = json.loads(
        (demo_dir / "tables" / "items" / "pixar.json").read_text()
    )
    table_config["name"] = ci_table_name
    shaped_create_table(json.dumps(table_config))

    engine_config = json.loads(
        (demo_dir / "engines" / "pixar_movies_items_only.json").read_text()
    )
    engine_config["name"] = ci_engine_name
    engine_config["data"]["item_table"]["query"] = (
        "SELECT cast, item_id, image_url, description, movie_title, "
        f"release_date FROM {ci_table_name}"
    )

    result = shaped_create_engine(json.dumps(engine_config))
    data = json.loads(result)
    assert "error" not in data, f"Create engine error: {data.get('error')}"

    # Immediately delete — don't wait for training
    result = shaped_delete_engine(ci_engine_name)
    data = json.loads(result)
    assert "error" not in data, f"Delete engine error: {data.get('error')}"


# ---------------------------------------------------------------------------
# List / query operations
# ---------------------------------------------------------------------------


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_shaped_list_engines_integration():
    """shaped_list_engines returns valid JSON with engines key from real API."""
    result = shaped_list_engines()
    data = json.loads(result)
    assert "error" not in data, f"API error: {data.get('error')}"
    assert "engines" in data
    assert isinstance(data["engines"], list)


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_shaped_list_tables_integration():
    """shaped_list_tables returns valid JSON with tables key from real API."""
    result = shaped_list_tables()
    data = json.loads(result)
    assert "error" not in data, f"API error: {data.get('error')}"
    assert "tables" in data
    assert isinstance(data["tables"], list)


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_shaped_list_views_integration():
    """shaped_list_views returns valid JSON with views key from real API."""
    result = shaped_list_views()
    data = json.loads(result)
    assert "error" not in data, f"API error: {data.get('error')}"
    assert "views" in data
    assert isinstance(data["views"], list)


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_shaped_global_query_integration():
    """shaped_global_query runs a catalog query against a listed table."""
    list_result = shaped_list_tables()
    list_data = json.loads(list_result)
    assert "error" not in list_data, f"API error: {list_data.get('error')}"
    tables = list_data.get("tables") or []
    assert tables, "Expected at least one table for global query E2E"
    table_name = tables[0]["name"]
    query = f"SELECT * FROM data.{table_name} LIMIT 1"
    result = shaped_global_query(query)
    data = json.loads(result)
    assert "error" not in data, f"API error: {data.get('error')}"


# ---------------------------------------------------------------------------
# Resources
# ---------------------------------------------------------------------------


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_account_resource_integration():
    """shaped://account returns engines, tables, and views keys."""
    result = account_resource()
    data = json.loads(result)
    assert "error" not in data, f"Resource error: {data.get('error')}"
    assert "engines" in data
    assert "tables" in data
    assert "views" in data


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_docs_shapedql_resource_integration():
    """shaped://docs/shapedql returns non-empty ShapedQL documentation."""
    result = docs_shapedql_resource()
    assert result, "Expected non-empty docs content"
    assert len(result) > 100


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_engine_resource_integration(ci_table_name, ci_engine_name):
    """shaped://engines/{name} returns engine config and sample keys."""
    demo_dir = _demo_dir()

    # Create backing table and engine
    table_config = json.loads(
        (demo_dir / "tables" / "items" / "pixar.json").read_text()
    )
    table_config["name"] = ci_table_name
    shaped_create_table(json.dumps(table_config))

    engine_config = json.loads(
        (demo_dir / "engines" / "pixar_movies_items_only.json").read_text()
    )
    engine_config["name"] = ci_engine_name
    engine_config["data"]["item_table"]["query"] = (
        "SELECT cast, item_id, image_url, description, movie_title, "
        f"release_date FROM {ci_table_name}"
    )
    shaped_create_engine(json.dumps(engine_config))

    result = engine_resource(ci_engine_name)
    data = json.loads(result)
    assert "error" not in data, f"Resource error: {data.get('error')}"
    assert "engine" in data
    assert "sample" in data


# ---------------------------------------------------------------------------
# Prompts
# ---------------------------------------------------------------------------


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_shaped_build_query_prompt_integration():
    """shaped-build-query prompt includes engine name and use case."""
    result = shaped_build_query_prompt(
        engine_name="my_engine", use_case="find similar items"
    )
    assert "my_engine" in result
    assert "find similar items" in result


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_shaped_build_query_prompt_with_user_id_integration():
    """shaped-build-query prompt includes personalisation text when user_id is given."""
    result = shaped_build_query_prompt(
        engine_name="my_engine", use_case="recommendations", user_id="u123"
    )
    assert "u123" in result
    assert "personalized" in result.lower()


@pytest.mark.integration
@pytest.mark.skipif(not _has_api_key(), reason="SHAPED_API_KEY not set")
def test_shaped_build_query_prompt_without_user_id_integration():
    """shaped-build-query prompt mentions content-based strategy when no user_id."""
    result = shaped_build_query_prompt(
        engine_name="my_engine", use_case="recommendations"
    )
    assert "content-based" in result.lower()
