"""Type-coerced result comparison for compliance tests."""

from __future__ import annotations

from opencypher_compliance.adapters import QueryResult


def _coerce(value):
    """Coerce a value to a common Python type for comparison.

    Handles int/float equivalence (5 == 5.0), None/null, and nested structures.
    """
    if value is None:
        return None
    if isinstance(value, bool):
        return value
    if isinstance(value, (int, float)):
        # Normalize: if float is a whole number, treat as int for comparison
        if isinstance(value, float) and value == int(value):
            return int(value)
        return value
    if isinstance(value, str):
        return value
    if isinstance(value, (list, tuple)):
        return [_coerce(v) for v in value]
    if isinstance(value, dict):
        return {k: _coerce(v) for k, v in value.items()}
    # For unknown types (e.g. database-specific), convert to string
    return str(value)


def _rows_equal(actual_rows: list[list], expected_rows: list[list]) -> bool:
    """Compare rows with type coercion."""
    if len(actual_rows) != len(expected_rows):
        return False
    for actual_row, expected_row in zip(actual_rows, expected_rows):
        if len(actual_row) != len(expected_row):
            return False
        for a, e in zip(actual_row, expected_row):
            if _coerce(a) != _coerce(e):
                return False
    return True


def compare_result(
    result: QueryResult,
    test: dict,
    error: Exception | None = None,
) -> dict:
    """Compare a query result against a test definition.

    Args:
        result: The QueryResult from the adapter (may be None if error occurred).
        test: The test definition dict from the catalog.
        error: An exception if the query raised one, or None.

    Returns:
        {"passed": bool, "reason": str | None}
    """
    # Handle expect_error
    if test.get("expect_error"):
        if error is not None:
            return {"passed": True, "reason": None}
        return {"passed": False, "reason": "Expected an error but query succeeded"}

    # If there was an error but we didn't expect one
    if error is not None:
        return {"passed": False, "reason": f"Query error: {error}"}

    # expect_success: just check query ran without error
    if test.get("expect_success"):
        return {"passed": True, "reason": None}

    # Check expected_columns
    expected_columns = test.get("expected_columns")
    if expected_columns is not None:
        if result.columns != expected_columns:
            return {
                "passed": False,
                "reason": f"Column mismatch: expected {expected_columns}, got {result.columns}",
            }

    # Check expected_rows (exact match with type coercion)
    expected_rows = test.get("expected_rows")
    if expected_rows is not None:
        if not _rows_equal(result.rows, expected_rows):
            return {
                "passed": False,
                "reason": f"Row mismatch: expected {expected_rows}, got {result.rows}",
            }

    # Check expected_contains (subset match)
    expected_contains = test.get("expected_contains")
    if expected_contains is not None:
        for expected_row in expected_contains:
            coerced_expected = [_coerce(v) for v in expected_row]
            found = False
            for actual_row in result.rows:
                coerced_actual = [_coerce(v) for v in actual_row]
                if coerced_actual == coerced_expected:
                    found = True
                    break
            if not found:
                return {
                    "passed": False,
                    "reason": f"Expected row {expected_row} not found in results",
                }

    return {"passed": True, "reason": None}
