"""Test validation and error handling."""

import pytest

from sidemantic import Dimension, Metric, Model, Relationship
from sidemantic.validation import (
    MetricValidationError,
    ModelValidationError,
    QueryValidationError,
)


def test_model_has_default_primary_key(layer):
    """Test that models have a default primary key."""
    # Model without explicit primary_key should default to "id"
    model = Model(
        name="orders",
        table="orders",
        relationships=[Relationship(name="customers", type="many_to_one", foreign_key="customer_id")],
        dimensions=[Dimension(name="status", type="categorical")],
        metrics=[],
    )

    layer.add_model(model)
    assert model.primary_key == "id"


def test_model_validation_no_table(layer):
    """Test that models without a physical, SQL, or source URI definition are rejected."""
    invalid_model = Model(
        name="orders",
        primary_key="id",
        dimensions=[],
        metrics=[],
    )

    with pytest.raises(ModelValidationError) as exc_info:
        layer.add_model(invalid_model)

    assert "must have one of 'table', 'sql', or 'source_uri' defined" in str(exc_info.value)


def test_source_uri_model_validates_but_python_compile_is_not_supported(layer):
    """source_uri-only models can load, but Python SQL generation cannot query them yet."""
    layer.add_model(
        Model(
            name="events",
            source_uri="s3://warehouse/events.parquet",
            primary_key="event_id",
            dimensions=[],
            metrics=[Metric(name="event_count", agg="count")],
        )
    )

    with pytest.raises(ValueError) as exc_info:
        layer.compile(metrics=["events.event_count"])

    message = str(exc_info.value)
    assert "source_uri" in message
    assert "Python SQL generation does not load source_uri data" in message


def test_metric_validation_simple_no_measure():
    """Test that Pydantic rejects invalid metric types at model creation time."""
    # Try to create metric with invalid type - should fail at Pydantic validation
    with pytest.raises(Exception) as exc_info:
        Metric(name="bad_metric", type="invalid_type")

    assert "literal_error" in str(exc_info.value).lower() or "validation" in str(exc_info.value).lower()


def test_metric_validation_measure_not_found(layer):
    """Test that metrics referencing non-existent measures are rejected."""
    # Add valid model
    layer.add_model(
        Model(
            name="orders",
            table="orders",
            primary_key="id",
            dimensions=[],
            metrics=[Metric(name="revenue", agg="sum", sql="amount")],
        )
    )

    # Try to reference non-existent measure
    invalid_metric = Metric(name="bad_metric", sql="orders.nonexistent")

    with pytest.raises(MetricValidationError) as exc_info:
        layer.add_metric(invalid_metric)

    assert "measure 'nonexistent' not found" in str(exc_info.value)


def test_metric_validation_self_reference(layer):
    """Test that self-referencing metrics are detected."""
    # Add model
    layer.add_model(
        Model(
            name="orders",
            table="orders",
            primary_key="id",
            dimensions=[],
            metrics=[Metric(name="revenue", agg="sum", sql="amount")],
        )
    )

    # Try to add self-referencing metric
    # Note: dependencies are auto-detected from expr now
    invalid_metric = Metric(
        name="metric_a",
        type="derived",
        sql="metric_a + 1",
    )

    with pytest.raises(MetricValidationError) as exc_info:
        layer.add_metric(invalid_metric)

    assert "cannot reference itself" in str(exc_info.value)


def test_query_validation_metric_not_found(layer):
    """Test that queries with non-existent metrics fail validation."""
    layer.add_model(
        Model(
            name="orders",
            table="orders",
            primary_key="id",
            dimensions=[Dimension(name="status", type="categorical")],
            metrics=[Metric(name="revenue", agg="sum", sql="amount")],
        )
    )

    with pytest.raises(QueryValidationError) as exc_info:
        layer.compile(metrics=["nonexistent_metric"], dimensions=["orders.status"])

    assert "Metric 'nonexistent_metric' not found" in str(exc_info.value)


def test_query_validation_accepts_multidot_graph_metric_name(layer):
    """Exact graph metric names with multiple dots should not be split as model.metric."""
    layer.add_model(
        Model(
            name="orders",
            table="orders",
            primary_key="id",
            dimensions=[Dimension(name="status", type="categorical", sql="status")],
            metrics=[Metric(name="revenue", agg="sum", sql="amount")],
        )
    )
    layer.add_metric(Metric(name="company.sales.revenue", sql="orders.revenue"))

    sql = layer.compile(metrics=["company.sales.revenue"], dimensions=["orders.status"])

    assert '"company.sales.revenue"' in sql
    assert "amount AS revenue_raw" in sql


def test_graph_metric_exact_name_wins_over_model_metric_reference(layer):
    """Exact graph metric names should resolve before model.metric interpretation."""
    layer.add_model(
        Model(
            name="orders",
            table="orders",
            primary_key="id",
            dimensions=[Dimension(name="status", type="categorical", sql="status")],
            metrics=[Metric(name="revenue", agg="sum", sql="amount")],
        )
    )
    layer.add_metric(Metric(name="orders.revenue", sql="SUM(orders.amount) * 2"))

    sql = layer.compile(metrics=["orders.revenue"], dimensions=["orders.status"])

    assert 'AS "orders.revenue"' in sql
    assert "* 2" in sql


def test_query_validation_dimension_not_found(layer):
    """Test that queries with non-existent dimensions fail validation."""
    layer.add_model(
        Model(
            name="orders",
            table="orders",
            primary_key="id",
            dimensions=[Dimension(name="status", type="categorical")],
            metrics=[Metric(name="revenue", agg="sum", sql="amount")],
        )
    )

    with pytest.raises(QueryValidationError) as exc_info:
        layer.compile(metrics=["orders.revenue"], dimensions=["orders.nonexistent"])

    assert "Dimension 'nonexistent' not found" in str(exc_info.value)


def test_query_validation_no_join_path(layer):
    """Test that queries requiring non-existent joins fail validation."""
    # Add two disconnected models
    layer.add_model(
        Model(
            name="orders",
            table="orders",
            primary_key="id",
            dimensions=[Dimension(name="status", type="categorical")],
            metrics=[Metric(name="revenue", agg="sum", sql="amount")],
        )
    )

    layer.add_model(
        Model(
            name="products",
            table="products",
            primary_key="id",
            dimensions=[Dimension(name="category", type="categorical")],
            metrics=[],
        )
    )

    with pytest.raises(QueryValidationError) as exc_info:
        layer.compile(metrics=["orders.revenue"], dimensions=["products.category"])

    # Order of models in error message may vary
    assert "No join path found between models" in str(exc_info.value)
    assert "'orders'" in str(exc_info.value)
    assert "'products'" in str(exc_info.value)


def test_query_validation_invalid_granularity(layer):
    """Test that invalid time granularities are rejected."""
    layer.add_model(
        Model(
            name="orders",
            table="orders",
            primary_key="id",
            dimensions=[Dimension(name="order_date", type="time", granularity="day", sql="created_at")],
            metrics=[Metric(name="revenue", agg="sum", sql="amount")],
        )
    )

    with pytest.raises(QueryValidationError) as exc_info:
        layer.compile(metrics=["orders.revenue"], dimensions=["orders.order_date__invalid_granularity"])

    assert "Invalid time granularity 'invalid_granularity'" in str(exc_info.value)


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
