"""
Property-Based Tests using Hypothesis

These tests use Hypothesis to generate random valid inputs and verify
that invariants hold across all inputs.
"""
from __future__ import annotations

from hypothesis import given, strategies as st

import pytest


@given(query=st.text(min_size=1, max_size=1000))
def test_query_never_crashes_on_string_input(query: str):
    """Property: Any string input should not crash the query parser."""
    from spice_mcp.adapters.dune import extract
    
    # Should either return a result or raise a specific exception, not crash
    try:
        result = extract.query(query, poll=False, api_key="test")
        # If it succeeds, should return a dict
        assert isinstance(result, dict)
    except Exception as e:
        # Should raise a specific exception, not crash
        assert len(str(e)) > 0
        assert isinstance(e, Exception)


@given(
    limit=st.one_of(st.none(), st.integers(min_value=0, max_value=100000)),
    offset=st.one_of(st.none(), st.integers(min_value=0, max_value=100000)),
)
def test_limit_offset_combinations_never_crash(limit: int | None, offset: int | None):
    """Property: Any limit/offset combination should not crash."""
    from spice_mcp.mcp.tools.execute_query import ExecuteQueryTool
    from spice_mcp.config import Config, DuneConfig
    from spice_mcp.logging.query_history import DisabledQueryHistory
    
    class FakeQueryService:
        def execute(self, **kwargs):
            return {
                "rowcount": kwargs.get("limit", 10) or 10,
                "columns": ["col"],
                "data_preview": [{"col": 1}],
                "execution": {"execution_id": "test"},
                "duration_ms": 100,
            }
    
    tool = ExecuteQueryTool(
        Config(dune=DuneConfig(api_key="test")),
        FakeQueryService(),
        DisabledQueryHistory(),
    )
    
    try:
        result = tool.execute(query="SELECT 1", limit=limit, offset=offset, format="preview")
        assert result["type"] == "preview"
    except Exception as e:
        # Should handle gracefully, not crash
        assert len(str(e)) > 0


@given(
    param_key=st.text(min_size=1, max_size=50, alphabet=st.characters(whitelist_categories=("L", "N", "_"))),
    param_value=st.one_of(
        st.text(max_size=100),
        st.integers(),
        st.floats(allow_nan=False, allow_infinity=False),
        st.booleans(),
    ),
)
def test_parameter_handling_never_crashes(param_key: str, param_value: str | int | float | bool):
    """Property: Any parameter key/value combination should not crash."""
    from spice_mcp.mcp.tools.execute_query import ExecuteQueryTool
    from spice_mcp.config import Config, DuneConfig
    from spice_mcp.logging.query_history import DisabledQueryHistory
    
    class FakeQueryService:
        def execute(self, **kwargs):
            return {
                "rowcount": 1,
                "columns": ["col"],
                "data_preview": [{"col": 1}],
                "execution": {"execution_id": "test"},
                "duration_ms": 100,
            }
    
    tool = ExecuteQueryTool(
        Config(dune=DuneConfig(api_key="test")),
        FakeQueryService(),
        DisabledQueryHistory(),
    )
    
    try:
        result = tool.execute(
            query="SELECT 1",
            parameters={param_key: param_value},
            format="preview",
        )
        assert result["type"] == "preview"
    except Exception as e:
        # Should handle gracefully, not crash
        assert len(str(e)) > 0


@given(schema=st.text(min_size=1, max_size=100))
def test_schema_name_handling_never_crashes(schema: str):
    """Property: Any schema name should not crash discovery."""
    from spice_mcp.mcp import server
    
    # Mock the discovery service
    class FakeDiscoveryService:
        def find_schemas(self, keyword: str) -> list[str]:
            return []
        
        def list_tables(self, schema: str, limit: int | None = None):
            return []
        
        def describe_table(self, schema: str, table: str):
            raise ValueError(f"Table {schema}.test not found")
    
    try:
        # Should handle any schema name gracefully
        fake = FakeDiscoveryService()
        result = fake.list_tables(schema)
        assert isinstance(result, list)
    except Exception as e:
        # Should raise specific error, not crash
        assert len(str(e)) > 0


@given(query_id=st.one_of(
    st.integers(min_value=1, max_value=999999999),
    st.text(min_size=1, max_size=50),
))
def test_query_id_formats_never_crash(query_id: int | str):
    """Property: Any query ID format should not crash."""
    from spice_mcp.adapters.dune import extract
    
    try:
        result = extract.query(query_id, poll=False, api_key="test")
        # Should return a dict (execution or error)
        assert isinstance(result, dict)
    except Exception as e:
        # Should raise specific error, not crash
        assert len(str(e)) > 0


@given(format_type=st.sampled_from(["preview", "raw", "metadata", "poll"]))
def test_format_types_never_crash(format_type: str):
    """Property: All format types should work without crashing."""
    from spice_mcp.mcp.tools.execute_query import ExecuteQueryTool
    from spice_mcp.config import Config, DuneConfig
    from spice_mcp.logging.query_history import DisabledQueryHistory
    
    class FakeQueryService:
        def execute(self, **kwargs):
            return {
                "rowcount": 1,
                "columns": ["col"],
                "data_preview": [{"col": 1}],
                "execution": {"execution_id": "test"},
                "duration_ms": 100,
            }
        
        def fetch_metadata(self, **kwargs):
            return {
                "metadata": {"state": "ok"},
                "next_uri": None,
                "next_offset": None,
            }
    
    tool = ExecuteQueryTool(
        Config(dune=DuneConfig(api_key="test")),
        FakeQueryService(),
        DisabledQueryHistory(),
    )
    
    try:
        result = tool.execute(query="SELECT 1", format=format_type)
        # Should return a dict with expected structure
        assert isinstance(result, dict)
        if format_type == "poll":
            assert result["type"] == "execution"
        elif format_type == "metadata":
            assert result["type"] == "metadata"
        elif format_type == "raw":
            assert result["type"] == "raw"
        else:
            assert result["type"] == "preview"
    except Exception as e:
        # Should handle gracefully, not crash
        assert len(str(e)) > 0

